写点什么

【Jvm】Jvm 类加载机制

  • 2022 年 5 月 16 日
  • 本文字数:2065 字

    阅读完需:约 7 分钟


  1. 文件格式验证:如是否以魔数 0xCAFEBABE 开头、主、次版本号是否在当前虚拟机处理范围之内、常量合理性验证等。


此阶段保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java类型信息的要求。


  1. 元数据验证:是否存在父类,父类的继承链是否正确,抽象类是否实现了其父类或接口之中要求实现的所有方法,字段、方法是否与父类产生矛盾等。


第二阶段,保证不存在不符合 Java 语言规范的元数据信息。


  1. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。例如保证跳转指令不会跳转到方法体以外的字节码指令上。


  1. 符号引用验证:在解析阶段中发生,保证可以将符号引用转化为直接引用。


可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

[](()准备

类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配。


public static int value = 123


在准备阶段过后的初始值是 0 而不是 123,因为这个时候没有正在的执行过 java 代码;而把 value 赋值为 123 的putstatic指令是程序被编译后,存放在类构造器clinit()方法中,将会在初始化的时候执行


特殊情况:final 修饰


public final static int value = 123


被 final 修饰之后就是常量属性,在准备阶段虚拟机就会直接赋值,不需要等到初始化阶段;

[](()解析

虚拟机将常量池内的符号引用替换为直接引用的过程。


解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。


为支持运行时绑定,解析过程在某些情况下可在初始化之后再开始,除解析过程外的其他加载过程必须按照如图顺序开始


NoClassDeFoundError:发生在类生命周期中解析阶段找不到相应的类

[](()初始化

到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行 clinit() 方法的过程。


clinit() 方法是由编译器按语句在源文件中出现的顺序依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。(不包括构造器中的语句。构造器是初始化对象的,类加载完成后,创建对象时候将调用的 init() 方法来初始化对象)


静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问


如下:



这样的写法是正常的; 但是如果把声明换个位置



这样就会提示 非法前向引用 ; 进而说明 clinit() 方法是由编译器按语句在源文件中出现的顺序 依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的


总结一下:


  1. 初始化接单是执行 clinit() 方法的过程


  1. clinit() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的


  1. clinit() 不需要显式调用父类,的初始化方法 clinit(),虚拟机会保证在子类的 clinit() 方法执行之前,父类的 clinit() 方法已经执行完毕,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。


  1. clinit() 方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 clinit() 方法


  1. 虚拟机会保证一个类的 clinit() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 clinit() 方法,其他线程都需要阻塞等待,直到活动线程执行 clinit() 方法完毕。


对于初始化阶段,虚拟机规范规定了有且只有 5 种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始)


  1. 遇到 new、getstatic 和 putstatic 或 invokestatic 这 4 条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。对应场景是:使用 new 实例化对象、读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法。


  1. 对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。


  1. 当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)


  1. 虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),


虚拟机会先初始化这个主类。


  1. 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。


[](()类加载器




即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类也不相等。

[](()双亲委派模型

双亲委派模型的加载类逻辑可参考如下代码:



// 代码摘自《深入理解 Java 虚拟机》


protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {


// 首先,检查请求的类是否已经被加载过了


Class c = findLoadedClass(name);


if (c == null) {


try {


if (parent != null) {


c = parent.loadClass(name, false);


} else {


c = findBootstrapClassOrNull(name);


}

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
【Jvm】Jvm类加载机制_Java_爱好编程进阶_InfoQ写作社区