JVM——类加载与字节码技术(3)

  • Post author:
  • Post category:其他




四、类加载阶段



4.1 加载




将类的字节码载入方法区(1.8后为元空间,在本地内存中)中,内部采用 C++ 的 instanceKlass

● _java_mirror 即 java 的类镜像,例如对 String 来说,它的镜像类就是 String.class,作用是把 klass 暴露给 java 使用

● _super 即父类

● _fields 即成员变量

● _methods 即方法

● _constants 即常量池

● _class_loader 即类加载器

● _vtable 虚方法表

● _itable 接口方法




如果这个类还有父类没加载,先加载父类




加载和链接可能是交替运行的

在这里插入图片描述


注意



● instanceKlass这样的【元数据】是存储在方法区 (1.8后的元空间内),但_java_mirror是存储在堆中(类在加载的同时会在堆内存中生成一个java_mirror的对象,如Person类会产生一个Person.class)

● 可以通过前面介绍的HSDB工具查看



4.2 链接




验证:验证类是否符合JVM规范,安全性检查




准备:为static变量分配空间,设置默认值

● static变量在JDK 7之前存储于instanceKlass末尾,从JDK 7开始,存储于_ java_ mirror末尾

● static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成

● 如果static变量是final的基本类型,那么编译阶段值就确定了,赋值在准备阶段完成

● 如果static变量是final的,但属于引用类型(new对象),那么赋值也会在初始化阶段完成




解析:将常量池中的符号引用解析为直接引用


HSDB的使用


● 先获得要查看的进程ID

jps

● 打开HSDB

java -cp D:\JAVA\JDK8.0\lib\sa-jdi.jar sun.jvm.hotspot.HSDB

● 运行时可能会报错,是因为缺少一个.dll的文件,我们在JDK的安装目录中找到该文件,复制到缺失的文件下即可

在这里插入图片描述

● 定位需要的进程

在这里插入图片描述

在这里插入图片描述

● 未解析时,常量池中的看到的对象仅是符号,未真正的存在于内存中

public class Demo1 {

   public static void main(String[] args) throws IOException, ClassNotFoundException {
      ClassLoader loader = Demo1.class.getClassLoader();
      //只加载不解析
      Class<?> c = loader.loadClass("com.nyima.JVM.day8.C");
      //用于阻塞主线程
      System.in.read();
   }
}

class C {
   D d = new D();
}

class D {

}

● 打开HSDB(可以看到此时只加载了类C)

在这里插入图片描述

查看类C的常量池,可以看到类D未被解析,只是存在于常量池中的符号

在这里插入图片描述

● 解析以后,会将常量池中的符号引用解析为直接引用

可以看到,此时已加载并解析了类C和类D

在这里插入图片描述

在这里插入图片描述



4.3 初始化

初始化阶段就是执行类构造器clinit()方法的过程,虚拟机会保证这个类的『构造方法』的线程安全

● clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的


注意


编译器收集的顺序是由语句在源文件中


出现的顺序决定


的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它


之后


的变量,在前面的静态语句块


可以赋值,但是不能访问


,如

在这里插入图片描述


发生时机


类的初始化的懒惰的,以下情况会初始化


● main 方法所在的类,总会被首先初始化

● 首次访问这个类的静态变量或静态方法时

● 子类初始化,如果父类还没初始化,会引发(子类联动父类初始化)

● 子类访问父类的静态变量,只会触发父类的初始化

● Class.forName

● new 会导致初始化


以下情况不会初始化


● 访问类的 static final 静态常量(基本类型和字符串)【在准备阶段,static final的常量就已经完成赋值,以后便可直接使用】

● 类对象.class 不会触发初始化(*.class类加载时就会生成mirror对象)

● 创建该类对象的数组(数组会存放在mirror对象中,加载时就会创建)

● 类加载器的.loadClass方法

● Class.forNamed的参数2为false时

● 验证类是否被初始化,可以看改类的静态代码块是否被执行



五、类加载——练习



练习1

从字节码的角度分析,使用a,b,c这三个常量是否会导致类E初始化







结论



:a、b不会;c会




在这里插入图片描述

未执行静态代码块(

类E为未进行初始化



在这里插入图片描述

a、b均为静态常量(基本类型与字符串常量),都在类链接的准备阶段进行赋值,并不会导致初始化;而c不为基本类型(包装类型),底层会做自动的装箱操作将基本类型转换为包装类型,虽然前面为静态的但是也不会在准备阶段完成,只能推迟到初始化阶段完成。



练习2

典型应用——完成懒惰初始化单例模式

在这里插入图片描述

以上的实现特点是:

◆ 懒惰实例化

◆ 初始化时线程安全是有保障的



版权声明:本文为m0_56188609原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。