Java类编译加载执行过程
JVM在执行某个类的时候,需要经过编译、加载、链接、初始化和运行时编译等过程。整个过程如下图所示:
接下来,我们详细了解一下Java类从编译到运行的整个过程。
一、类编译
在我们编写好java源代码后,需要将.java源代码文件编译成.class字节码文件,这样代码才能在JVM虚拟机上正常运行。.java文件的编译通常是由JDK中自带的javac工具完成的,对于一个简单的.java文件,我们可以通过javac命令生成.class文件。
二、类加载
当一个类被创建实例或者被其它对象引用时,如果虚拟机没有加载过这个类,那么就会通过类加载器将字节码文件(.java文件)加载到内存中。
不同的实现类是由不同的类加载器进行加载的。JDK中的本地方法类一般由根加载器(Bootstrap loader)加载,JDK中内部实现的扩展类一般由扩展加载器(ExtClassLoader)加载,而程序中的类文件则由系统加载器(AppClassLoader)加载。
在类加载后,class类文件中的常量池信息以及其它数据都会被保存到JVM内存的方法区中。
三、类连接
当类被加载到JVM内存中后,会进行连接操作,这个过程又包括验证、准备和解析三个部分:
-
验证,验证类是否符合Java规范和JVM规范,在保证符合规范的前提下,避免危害虚拟机安全。
-
准备,为类的静态变量分配内存,初始化为系统的初始值。对于final static修饰的变量,则会直接赋值为用户的定义值。
例如,
private final static int valut = 123
,会在准备阶段分配内存,并初始化值为123;而如果是private static int value = 123
,在准备阶段value的值仍然为0。 -
解析,将符号引用转为直接引用。在编译时,java类并不知道所引用类的实际地址,因此只能使用符号引用来代替。class文件的常量池中存储了符号引用,包括类和接口的全限定名、类引用、方法引用以及成员变量引用等。如果要使用这些类和方法,就需要把它们转化为JVM可以直接获取的内存地址或指针,即直接引用。
四、类初始化
在完成连接操作后,还要进行类初始化工作,在这个阶段中,JVM首先会执行构造器方法(即<clinit>
方法)。编译器在将.java文件编译成.class文件时,会将所有的类初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起组成为<clinet>
方法。
注:JVM会保证构造器方法的线程安全,即同一时间只有一个线程执行。
类初始化时,会将类的静态变量和静态代码块初始化为用户自定义的值,初始化的顺序和java源码从上到下的顺序一致。例如:
1 |
|
此时的运行结果为:
1 |
|
而如下代码:
1 |
|
运行结果为:
1 |
|
子类初始化时,首先会调用父类的构造器方法,然后再执行子类的构造器方法,如下代码:
1 |
|
运行结果为:
1 |
|
初始化代码时,如果实例化一个新对象,则会调用<init>
方法对实例变量进行初始化,并会执行对应的构造方法内的代码。
五、运行时编译
完成初始化后,类在调用执行过程中,执行引擎会把类的字节码转化为机器码,然后才会在操作系统中执行类。这里将在将字节码转化为机器码的过程,就是运行时编译。
六、小结
在本篇文章中,我们主要讨论了java类的编译加载执行过程,这个过程包括:
- 类编译,将源码文件(.java文件)编译成字节码文件(.class文件)
- 类加载,将字节码文件加载到内存中
- 类链接,对内存中的类进行验证、准备、解析等一系列操作
- 类初始化,执行类的构造器方法
- 运行时编译,将类的字节码转为化机器