我们平时在编译java代码之后,会生成.class文件——字节码文件,JVM会执行字节码文件,JVM执行字节码文件的过程为下面7个阶段
1. 加载
2. 验证
3. 准备
4. 解析
5. 初始化
6. 使用
7. 卸载
加载
简单的说,就是把字节码文件加载到内存中
验证
当JVM加载完Class字节码文件,并在方法区创建对应的Class对象之后,JVM会启动对改字节码流的校验,只有符合JVM字节码规范的文件才能被JVM正确执行,校验过程主要为如下:
JVM规范校验:JVM 会对字节流进行文件格式校验,判断其是否符合 JVM 规范,是否能被当前版本的虚拟机处理等
代码逻辑校验:JVM 会对代码组成的数据流和控制流进行校验,确保 JVM 运行该字节码文件后不会出现致命错误。比如传参、返参是否正确等
准备
当完成字节码的校验之后,jvm为类变量分配内存以及初始化,但是有下面的点需要注意
内存分配的对象
java中的变量有2种类型,分别为类变量和类成员变量
- 类变量:被static修饰的变量
- 类成员变量:除了类变量的其他变量
在准备阶段,jvm只会为类变量分配内存,而不会为类成员变量分配内存,到初始化阶段才会为类成员变量分配内存
初始化的类型
在准备阶段,jvm会为类变量分配内存,并为其初始化,但是这里的初始化和我们平时理解的初始化不太一样,这里的初始化是指为变量赋其类型在java中的零值,而不是用户代码里面的初始值,但是如果一个变量被static final修饰的话,则会被赋予用户所希望的值
示例:
在准备阶段,jvm会为其赋值为0,而不是1
public static int test = 1;
在准备阶段,jvm会为其赋值1
public static final int test2 = 1;
为什么同样是被static修饰,但是结果不一样,我们可以这么理解,在java中,被final修饰代表不可变,如果被static fianl修饰,在准备阶段被赋值为0,这样和java里面final的约定不一致,但是如果是赋值为1,那么就很合乎情理。没有被final修饰的变量,其可能在初始化阶段或者运行阶段发生一系列变化,所以就没有必要在准备阶段赋予用户所要的值。
解析
当通过准备阶段之后,jvm 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类引用进行解析。这个阶段的主要是将其在常量池中的符号引用替换成直接其在内存中的直接引用。
初始化
初始化阶段,用户定义的Java代码才真正开始执行,jvm会根据语句的执行顺序对类对象初始化,下面几种情况会触发初始化
jvm遇到new、getstatic、putstatic、invokestatic这4条字节码指令,如果类没有初始化过,就会先初始化,生成这几条指令一般是如下场景: - new:使用new关键字实例化对象
getstatic/putstatic:读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)
invokestatic调用一个类的静态方法
使用java.lang.reflect对类进行反射调用的时候,如果类没初始化要先初始化
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
使用
jvm初始化完之后,就会从方法入口,执行用户的代码
卸载
当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存