jvm:类加载的三阶段

类加载阶段:

  • 将类的字节码加载到方法区中,内部采用C++的instanceKlass描述java类;他的重要属性:
    • _java_mirror:即java的类镜像,eg: String.class,等效为一个java和C++的桥梁,作用时把C++的Klass暴露给java使用;
    • _super:即父类
    • _fields:即成员变量;
    • _constants:常量池;
    • _vtable:虚方法表【使用重载方法的,实现动态绑定】;
    • _itable:接口方法表;
  • 如果这个类还有父类没有加载,优先加载父类;
  • 加载和链接可能交替运行;
  • 注意事项:
    • instanceKlass的【元数据】时存储到方法区的【1.8以后时在元空间】;
    • 但是_java_mirror是存储到堆空间的,java实例对象通过找到_java_mirror【类.class,反射】,然后_java_mirror是指向instanceKlass的,从而获取相关方法;
      • 类名.java,在堆中生成,同时持有元空间中instanceKlass的地址,同时instanceKlass中的_java_mirror的地址也指向类名.java
    • 是实例化对象的,对象头有16个字节,其中8个字节指向它的class地址;

类连接阶段:

  • 验证:
    • 验证类是否符合jvm的规范,安全性检查,eg:magic魔术值;
  • 准备:为static变量分配分配空间,设置默认值;
    • static在7.0以前是存放在instanKlass末尾,但是从7.0开始存放在堆中的.class后面;
    • static变量分配空间和赋值是两个操作,分配空间是在准备阶段完成,赋值是在初始阶段完成;
    • 如果static变量是 final 的基本变量和字符串常量,那么编译阶段就已经确定,因此可以直接在准备阶段完成赋值;
      • 当static变量虽然是final 变量,但属于引用类型,那么存在new 也必须的在初始化阶段完成;
    • loadClass()方法加载并不会导致类的解析和初始化;
  • 解析:将常量池中的符号引用为直接引用;
    • 常量池中的符号,可能是方法,属性,类等,并没有对应的引用地址;
    • 但是经过解析后,类可以获取对应堆的地址,
  • 初始化:
    • 初始化,即调用<cinit>()V方法,虚拟机会保证这个类的的初始化方法的线程安全;

初始化的时机【类的加载是懒惰的】:

  • main方法所在的类,总会被首先初始化;
  • 首次方法这个类的静态变量和静态方法的时候;
  • 子类初始化,如父类还没有初始化的时候;
  • 子类访问父类的静态变量,只会触发父类的初始化;
  • Class.forName【反射的时候,默认参数】;
  • new的时候;

不会导致初始化的情况:

  • 访问类的static final 静态常量【基本类型和字符串】;
  • 类对象.class不会触发【加载阶段生成的】;
  • 创建该类的数组不会触发;
  • 类加载器的loadClass()方法;
  • Class.forName的参数2为false的时候;
posted @ 2025-03-19 14:56  烟雨断桥  阅读(16)  评论(1)    收藏  举报