个人名片:

【实战JVM】类的生命周期_notepad++


🐼作者简介:一名大三在校生,喜欢AI编程🎋
🐻❄️个人主页🥇:落798. 🐼

  • RabbitMQ快速入门🔥 🐓每日一句:🍭我很忙,但我要忙的有意义!
    欢迎评论 💬点赞👍🏻 收藏 📂加关注+



文章目录

  • 类的生命周期
  • 1、 加载阶段
  • 2、 连接阶段
  • 3、验证
  • 4、准备
  • 5、解析
  • 6、初始化阶段


类的生命周期

类的生命周期描述了一个类加载、使用、卸载的整个过程。整体可以分为:

  • 加载
  • 连接,其中又分为验证、准备、解析三个子阶段
  • 初始化
  • 使用
  • 卸载

【实战JVM】类的生命周期_初始化_02

1、 加载阶段

1、加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息,程序员可以使用Java代码拓展的不同的渠道。

  • 从本地磁盘上获取文件
  • 运行时通过动态代理生成,比如Spring框架
  • Applet技术通过网络获取字节码文件
    2、类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中,方法区中生成一个InstanceKlass对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。

【实战JVM】类的生命周期_字节码_03

4、Java虚拟机同时会在堆上生成与方法区中数据类似的java.lang.Class对象,作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK8及之后)。

【实战JVM】类的生命周期_notepad++_04

2、 连接阶段

连接阶段分为三个子阶段:

  • 验证,验证内容是否满足《Java虚拟机规范》。
  • 准备,给静态变量赋初值。
  • 解析,将常量池中的符号引用替换成指向内存的直接引用。

【实战JVM】类的生命周期_Java_05

3、验证

验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。主要包含如下四部分,具体详见《Java虚拟机规范》:
1、文件格式验证,比如文件是否以0xCAFEBABE开头,主次版本号是否满足当前Java虚拟机版本要求。

【实战JVM】类的生命周期_Java_06

2、元信息验证,例如类必须有父类(super不能为空)。
3、验证程序执行指令的语义,比如方法内的指令执行中跳转到不正确的位置。
4、符号引用验证,例如是否访问了其他类中private的方法等。

对版本号的验证,在JDK8的源码中如下:

【实战JVM】类的生命周期_字节码_07

编译文件的主版本号不能高于运行环境主版本号,如果主版本号相等,副版本号也不能超过。

4、准备

准备阶段为静态变量(static)分配内存并设置初值,每一种基本数据类型和引用数据类型都有其初值。

如下代码:

public class Student{

public static int value = 1;

}

在准备阶段会为value分配内存并赋初值为0,在初始化阶段才会将值修改为1。

final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。
如下例子中,变量加上final进行修饰,在准备阶段value值就直接变成1了,因为final修饰的变量后续不会发生值的变更。

【实战JVM】类的生命周期_Java_08

来看这个案例:

public class HsdbDemo {
    public static final int i = 2;
    public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException {
        HsdbDemo hsdbDemo = new HsdbDemo();
        System.out.println(i);
        System.in.read();
    }
}

从字节码文件也可以看到,编译器已经确定了该字段指向了常量池中的常量2:

【实战JVM】类的生命周期_notepad++_09

5、解析

解析阶段主要是将常量池中的符号引用替换为直接引用,符号引用就是在字节码文件中使用编号来访问常量池中的内容。

【实战JVM】类的生命周期_Java_10

直接引用不在使用编号,而是使用内存中地址进行访问具体的数据。

【实战JVM】类的生命周期_notepad++_11

6、初始化阶段

初始化阶段会执行字节码文件中clinit(class init 类的初始化)方法的字节码指令,包含了静态代码块中的代码,并为静态变量赋值。
如下代码编译成字节码文件之后,会生成三个方法:

public class Demo1 {

    public static int value = 1;
    static {
        value = 2;
    }
   
    public static void main(String[] args) {

    }
}

【实战JVM】类的生命周期_字节码_12

  • init方法,会在对象初始化时执行
  • main方法,主方法
  • clinit方法,类的初始化阶段执行

继续来看clinit方法中的字节码指令:
1、iconst_1,将常量1放入操作数栈。此时栈中只有1这个数。

【实战JVM】类的生命周期_初始化_13


2、putstatic指令会将操作数栈上的数弹出来,并放入堆中静态变量的位置,字节码指令中#2指向了常量池中的静态变量value,在解析阶段会被替换成变量的地址。

【实战JVM】类的生命周期_notepad++_14

3、后两步操作类似,执行value=2,将堆上的value赋值为2。

如果将代码的位置互换:

public class Demo1 {
    static {
        value = 2;
    }
   
    public static int value = 1;
   
    public static void main(String[] args) {

    }
}

字节码指令的位置也会发生变化:

【实战JVM】类的生命周期_jvm_15

这样初始化结束之后,最终value的值就变成了1而不是2。

以下几种方式会导致类的初始化:
1.访问一个类的静态变量或者静态方法,注意变量是final修饰的并且等号右边是常量不会触发初始化。
2.调用Class.forName(String className)。
3.new一个该类的对象时。
4.执行Main方法的当前类。
添加-XX:+TraceClassLoading 参数可以打印出加载并初始化的类


欢迎评论 💬点赞👍🏻 收藏 📂加关注+