Java类编译加载执行过程

JVM在执行某个类的时候,需要经过编译、加载、链接、初始化和运行时编译等过程。整个过程如下图所示:

image-20200920122012583

接下来,我们详细了解一下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
2
3
4
5
6
7
8
9
private static int i=1;

static {
i=0;
}

public static void main(String [] args){
System.out.println(i);
}

此时的运行结果为:

1
0

而如下代码:

1
2
3
4
5
6
7
8
9
static {
i=0;
}

private static int i=1;

public static void main(String [] args){
System.out.println(i);
}

运行结果为:

1
1

子类初始化时,首先会调用父类的构造器方法,然后再执行子类的构造器方法,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Parent{
public static String parentStr = "parent static string";

static {
System.out.println("parent static fields");
System.out.println(parentStr);
}

public Parent(){
System.out.println("parent instance initialization");
}
}

public class Sub extends Parent{
public static String subStr = "sub static string";

static {
System.out.println("sub static fields");
System.out.println(subStr);
}

public Sub(){
System.out.println("sub instance initialization");
}

public static void main(String[] args){
System.out.println("sub main");
new Sub();
}
}

运行结果为:

1
2
3
4
5
6
7
parent static fields
parent static string
sub static fields
sub static string
sub main
parent instance initialization
sub instance initialization

初始化代码时,如果实例化一个新对象,则会调用<init>方法对实例变量进行初始化,并会执行对应的构造方法内的代码。

五、运行时编译

完成初始化后,类在调用执行过程中,执行引擎会把类的字节码转化为机器码,然后才会在操作系统中执行类。这里将在将字节码转化为机器码的过程,就是运行时编译。

六、小结

在本篇文章中,我们主要讨论了java类的编译加载执行过程,这个过程包括:

  • 类编译,将源码文件(.java文件)编译成字节码文件(.class文件)
  • 类加载,将字节码文件加载到内存中
  • 类链接,对内存中的类进行验证、准备、解析等一系列操作
  • 类初始化,执行类的构造器方法
  • 运行时编译,将类的字节码转为化机器

Java类编译加载执行过程
https://kuberxy.github.io/2020/09/20/Java类编译加载执行过程/
作者
Mr.x
发布于
2020年9月20日
许可协议