关于编译的过程

Java代码在进行javac编译的时候,并不像C/C++那样有“连接”这一步骤,而是在虚拟机加载class文件的时候进行动态连接。也就是说,在class文件中不会保存各个方法、字段的最终内存布局信息,因此这些方法、字段的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存入口地址之中。


类的加载过程

加载

Java文件编译了以后,将会生成class文件,存放至硬盘中,直到程序运行需要才会由类加载器加载至jvm的内存中。类加载器并不需要等到某个类被“主动使用”时再去加载,JVM规范允许类加载器在预料某个类将会被使用时就预先加载它。

在加载阶段,虚拟机需要完成以下3件事:
1) 通过一个类的全限定名来获取定义此类的二进制字节流;(可以是class文件、jar包、jsp应用、网络获取的applet、反射等)
2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3) 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;(并没有明确规定死在Java堆中,对于Hotspot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面),这个对象将作为程序访问方法区中的这些类型数据的外部接口

也就是说,当二进制字节流中的数据被转换为运行时数据结构时,实际上[方法区/堆]有一个java.lang.class对象的实例可以访问到方法区的各类数据,包括常量池表、方法表、字段表、字节码指令等。

验证

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。值得注意的是,此阶段进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值通常情况下是数据类型的零值,假设一个类变量的定义:

1
public static int value = 123;

那变量value在准备阶段过后的初始值是0而不是123,因为此时尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会进行。

如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假设上面类变量value的定义如下:

1
public static final int value = 123;

编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。

解析

类加载过程中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程(试想如果符号引用先到常量池中寻找符号,再找对应的类型,无疑会耗费更多时间)。

值得注意的是,JVM规范并没有规定解析的时间,解析阶段可以发生在类加载时期,也可以发生在符号引用将被使用时。

初始化

类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。
在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序中的值来赋值给类变量(注意是类变量)。假设一个类变量定义:

1
2
3
4
5
6
7
8
9
10
11
private static int a;
private static int b = 222;
private static final int c = 223;
static {
a = 221;
}
public static void main(String[] args) {}
private static int d = a + c;

在初始化阶段,虚拟机由上往下执行初始化,从字节码可见,类变量的赋值包裹在static块中,字节码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: sipush 222
3: putstatic #2 // Field b:I
6: sipush 221
9: putstatic #3 // Field a:I
12: getstatic #3 // Field a:I
15: sipush 223
18: iadd
19: putstatic #5 // Field d:I
22: return
LineNumberTable:
line 7: 0
line 11: 6
line 16: 12


参考资料

《深入理解Java虚拟机》,§ 7 虚拟机类加载机制