《分布式Java应用之基础与实践》读书笔记四

Java代码作为一门跨操作系统的语言,最终是运行在JVM中的,所以对于JVM的理解就变得非常重要了。整体上,我们可以从三个方面来深入理解JVM。

  • Java代码的执行
  • 内存管理
  • 线程资源同步和交互机制

  Java程序运行在JVM上,JVM的运行状况对于Java程序而言会产生很大的影响,因此,掌握JVM中的关键机制对于编写稳定、高性能的Java程序很重要。首先,我们看看JVM规范定义的标准结构图:

《分布式Java应用之基础与实践》读书笔记四

JVM负责转载class文件并执行,因此,首先要掌握的是JDK如何将Java代码编译为class文件、如何转载class文件及如何执行class。将源码编译为class文件的实现取决于各个JVM实现或各种源码编译器;class文件通常有类加载器来完成加载;class的执行在Sun JDK中有解释执行和编译为机器码执行两种方式,其中编译为机器码又分为client和server两种模式。Sun JDK为了提升class的执行效率,对于解释执行和编译为机器码执行都设置了很多的优化策略。Java程序无需显示的分配和回收内存,因此JVM如何进行内存的分配和回收也是要关注的问题。JVM提供了多线程的支持,对于分布式Java应用而言,通常要借助线程来实现高并发,因此JVM中线程资源同步的机制及线程之间交互的机制也是需要掌握的。

Java代码的执行机制

Java源码编译机制

  JVM规范定义了class文件的格式,但并没有定义Java源码如何编译成class文件,各厂商自行实现编译器,例如Sun JDK中的javac。

《分布式Java应用之基础与实践》读书笔记四

下面简单介绍以上三个步骤:

  • 分析和输入到符号表(Parse and Enter):Parse过程主要是词法分析和语法分析,将代码字符串 ---> token序列 ---> 抽象语法树;Enter过程主要将符号输入到符号表,包括确定类的超类型和接口、添加默认构造器,符号输入符号表等
  • 注解处理(Annotation Processing):主要处理用户自定义的annotation,可基于annotation来生成附加代码或进行一些特殊的检查。
  • 语义分析和生成class文件(Analyse and Generate):Analyse过程主要基于抽象语法树进行一系列的语义分析。包括将语法树中的名字、表达式等元素与变量、方法、类型等联系到一起;检查变量使用前是否已声明;推导泛型方法的类型参数;...;将含有语法糖的语法树改为含有简单语法结构的语法树等。Generate过程主要是将实例成员初始化器收集到构造器中,将静态成员初始化器收集为();将抽象语法树生成字节码,采用后序遍历语法树,并进行最后少量代码转换(String相加转变为StringBuilder操作);最后从符号表生产class文件。

  class文件中并不仅仅存放了字节码,还存放了很多辅助jvm来执行class的附加信息,一个class文件包括了一下信息:

  • 结构信息:格式版本号及各部分的数量与大小的信息
  • 元数据:可简单对应Java源码中"声明"与"常量"的信息
  • 方法信息:可简单对应Java源码中"语句"与"表达式"的信息

  假设有如下一段简单的代码:

public class Foo {
    private static final int MAX_COUNT=1000;
    private static int count=0;
    public int bar() throws Exception {
        if(++count >= MAX_COUNT) {
            count = 0;
            throw new Exception("count overflow");
        }
        return count;
    }
}

// 执行javac -g Foo.java 编译此源码,之后通过 javap -c -s -l -verbose Foo 来参看编译后的class文件

// 类继承的超类/实现的接口的声明信息
public class Foo extends java.lang.Object
    SourceFile: "Foo.java"
// class文件格式版本号
minor version: 0
major version: 50
// 常量池,存放所有Field名称、方法名、方法签名、类型名、代码及class文件中的常量值
Constant pool:
const #1 = Method #7.#27; // java/lang/Object."<init>":()V
const #2 = Field  #6.#28; // Foo.count:I
const #3 = class  #29;    // java/lang/Exception
const #4 = String #30;    // count overflow
const #5 = Method #3.#31; // java/lang/Exception."<init>":(Ljava/lang/String;)V
...
const #34 = Asciz (Ljava/lang/String;)V;
{
    // 将符号输入到符号表时生成的默认构造器方法
    public Foo();
    ...
    // bar方法的元数据信息
    public int bar() throws java.lang.Exception;
    Signature: ()I
    // 对应字节码的源码行号信息
    LineNumberTable:
        line 9: 0
        line 10: 15
        line 11: 19
    // 局部变量信息,和方法绑定,接口没有方法体,所以ASM之类的在获取接口方法时,是拿不到方法中参数的信息
    LocalVariable:
        Start   Length  Slot    Name    Signature
        0       33      0       this    LFoo;
    Code:
        Stack=3, Locals=1, Args_size=1
        // 方法对应的字节码
        0:getstatic     #2; // Field count:
        ...
        // 记录有分支的情况
        StackMapTable: number_of_entries=1
        frame_type=29 /* same */
        // 异常处理器表
        Exceptions:
            throws java.lang.Exception
        ...
}

从以上可见,class文件是个完整的自描述文件,字节码在其中只占了很小的部分,源码编译为class文件后,即可放入jvm中执行。执行时jvm首先要做的是装载class文件,这个机制称为类加载机制。

上一篇:《分布式Java应用之基础与实践》读书笔记三


下一篇:TZOJ 4024 游戏人生之梦幻西游(连续子段和绝对值最小)