4虚拟机类加载机制

虚拟机类加载机制

  • 1.8中的扩展类加载器在1.9中改为平台类加载器
  • 类的加载、连接、初始化都是在程序运行期间完成的

类加载的生命周期

  • 详解

    1. loading(按需动态加载,双亲委派机制)
      1. 自底向上检查该类是否已经加载parent方向
    2. linking
      1. Verfication
        1. 验证文件是否符合jjvm规定
      2. Preparation
        1. 静态变量赋默认值
      3. Resolution
        1. 将类、方法、属性等符号引用解析为直接引用
          常量池中的各种符号应用解析为指针、偏移量等内存地址的直接引用
    3. Initializing
      1. 变量赋初始值
  • 类的加载过程是按照这种顺序,而解析阶段则不一定。它在某种情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑(动态绑定)。这些阶段都是互相交叉混合进行的。

  • 类初始化的五种情况

    1. 遇到new、 getstatic、 putstatic或invokestatic这4条字节码指令时, 如果类没有进行过初始化, 则需要先触发其初始化。 生成这4条指令的最常见的Java代码场景是:
      1. 使用new关键字实例化对象的时候
      2. 读取或设置一个类的静态字段( 被final修饰、 已在编译期把结果放入常量池的静态字段除外) 的时候
      3. 调用一个类型的静态方法的时候
    2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    5. 当使用JDK 1.7的动态语言支持时, 如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、 REF_putStatic、 REF_invokeStatic的方法句柄, 并且这个方法句柄所对应的类没有进行过初始化, 则需要先触发其初始化。
    6. 当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化
  • 接口的初始化

    • 接口也有初始化过程,唯一和类有所区别的是初始化场景的第3种,当一个接口初始化,并不要求其父接口全部都完成了初始化,只有在真正用到父接口的时候(如引用接口中定义的常量)才会初始化

类加载的顺序

  1. 基类静态代码块,基类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)
  2. 派生类静态代码块,派生类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)
  3. 基类普通代码块,基类普通成员字段(并列优点级,按代码中出现先后顺序执行)
  4. 基类构造函数
  5. 派生类普通代码块,派生类普通成员字段(并列优点级,按代码中出现先后顺序执行)
  6. 派生类构造函数

加载

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据的访问入口。

验证

  1. 文件格式的验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证

初始化

<clinit>()方法

  • 由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的
  • 与类的构造函数不同,它不需要显式的调用父类构造器,Java虚拟机会保证在子类的<clinit>方法执行前,父类的<clinit>方法已经执行完毕
  • <clinit>方法对于类或者接口来说不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>方法
  • Java虚拟机必须保证一个类的<clinit>方法在多线程环境中被正确地加锁同步,如果多线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>方法,其他线程都需要阻塞等待

类加载器

  • 对于任意一个类,都必须由加载它的类加载和这个类本身一起共同确立其在java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间

  • image-20241104234342703

双亲委派

  • 加载顺序(自顶向下进行实际查找和加载child方向)
    1. Bootstrap
      1. 加载lib/rt.jar charset.jar等核心类或者被 -Xbootclasspath 参数所指定的路径中的类,C++实现
    2. Extension
      1. 加载扩展jar包,jre/lib/ext/*.jar,或由-Djava.exit.dirs指定,开发者可以直接使用扩展类加载器。
      2. JAVA代码实现
    3. App
      1. 负责加载用户类路径上所有的类库
      2. 加载classLoad指定内容,如果没有定义过自己的类加载器,这个程序中默认的加载器
    4. Custom ClassLoader
      1. 自定义ClassLoader
        1. 继承ClassLoader,重写findclass方法,实现loadclass方法
  • 打破双亲委派
    • 重写loadclass方法
    • 什么情况下打破
      • 热启动,热部署
        • tomcat都有自己的模块指定classloader

JDK9 模块化

  • img
  • EXtension class loader被Platfrom class loader 替换

  • 当平台及应用程序类加载器收到类加载请求,在委派给父加载器前,要先判断该类是否能后归属到某一个系统模块中,如果可以找到这样的归属关系,就要先委派给负责那么模块的加载器完成,这算双亲委派机制的的第四次破坏


4虚拟机类加载机制
https://x-leonidas.github.io/2022/02/01/04Java/JVM/4虚拟机类加载机制/
作者
听风
发布于
2022年2月1日
更新于
2024年11月5日
许可协议