05 Java类加载

2021-09-22  本文已影响0人  攻城老狮

1 类的生命周期

类加载阶段分为:加载,链接(验证,准备,解析),初始化(<init>()V方法)。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zMjI2NTU2OQ==,size_16,color_FFFFFF,t_70.png

2 加载

  1. 将类的字节码载入方法区中,内部采用 c++ 的 instanceKlass 描述 java 类,它的重要 field 域有:
  1. 如果这个类还有父类没有加载,先加载父类

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

  3. instanceKlass 这样的【元数据】是存储在方法区(1.8后的元空间内),但 _java_mirror是存储在堆中

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zMjI2NTU2OQ==,size_16,color_FFFFFF,t_70-20210922144804674.png

3 链接

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

用支持二进制的编辑器修改 class 文件的魔数cafebabe,在控制台运行。会报错,说明验证class文件失败。

image-20210922152133312.png

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

  1. Java代码
// 类加载分析 - 演示 final 对静态变量的影响
public class TestDemo {
    // 在准备阶段,仅仅只有分配空间,没有赋值
    static int a;
    // 在准备阶段,仅仅只有分配空间,赋值是在类的构造方法中(初始化阶段 <cinit>V())
    static int b = 10;
    // 在准备阶段,20 属于ConstantValue,final+static 修饰的整型变量 在准备阶段就完成了赋值,编译器对其优化了
    static final int c = 20;
    // 在准备阶段,"hello" 也属于ConstantValue,static+final 修饰的字符串常量 在准备阶段就完成了赋值,编译器对其优化
    static final String d = "hello";
    // 在准备阶段,如果 static 变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
    static final Object e = new Object();
}
  1. 反编译Java代码
{
  static int a; // 在准备阶段,仅仅只有分配空间,没有赋值
    descriptor: I
    flags: ACC_STATIC

  static int b; // 在准备阶段,仅仅只有分配空间,赋值是在类的构造方法中(初始化阶段 <cinit>V())
    descriptor: I
    flags: ACC_STATIC

  static final int c; // 在准备阶段,20 属于ConstantValue,final+static 修饰的整型变量 在准备阶段就完成了赋值,编译器对其优化了
    descriptor: I
    flags: ACC_STATIC, ACC_FINAL
    ConstantValue: int 20

  static final java.lang.String d; // 在准备阶段,"hello" 也属于ConstantValue,static+final 修饰的字符串常量 在准备阶段就完成了赋值,编译器对其优化
    descriptor: Ljava/lang/String;
    flags: ACC_STATIC, ACC_FINAL
    ConstantValue: String hello

  static final java.lang.Object e; // 在准备阶段,如果 static 变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
    descriptor: Ljava/lang/Object;
    flags: ACC_STATIC, ACC_FINAL

  public com.yqj.TestDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/yqj/TestDemo;

  static {}; //初始化阶段
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: bipush        10
         2: putstatic     #2                  // Field b:I
         5: new           #3                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putstatic     #4                  // Field e:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 9: 0
        line 15: 5
}

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

将常量池中的符号引用解析为直接引用。符号引用仅仅只是符号,并不知道类、方法、属性到底在内存的哪个位置;而直接引用就可以确切知道类、方法、属性在内存的中位置。

4 初始化

<cinit>()v 方法,初始化是类加载的最后一个阶段。初始化即调用 <cinit>()v,虚拟机会保证这个类的 【构造方法】的线程安全。类初始化是【懒惰的】。

  1. 类的初始化时机
  1. 不会导致类初始化的情况
  1. 测试类初始化的代码实例
public class TestDemo {
    static {
        // main 方法所在的类,总会被首先初始化
        System.out.println("main init");
    }
 
    public static void main(String[] args) throws ClassNotFoundException {
        // 不会导致类初始化的情况
        // 1、final static的基本类型静态常量不会触发初始化
        System.out.println(B.b);
        // 2、类对象.class 不会触发初始化
        System.out.println(B.class);
        // 3、创建该类的数组不会触发初始化
        System.out.println(new B[0]);
        // 4、不会初始化类 B,但会加载 B、A
        ClassLoader c1 = Thread.currentThread().getContextClassLoader();
        c1.loadClass("com.yqj.B");
        // 5、不会初始化类 B,但会加载 B、A
        ClassLoader c2 = Thread.currentThread().getContextClassLoader();
        Class.forName("com.yqj.B", false, c2);
 
        // 导致类初始化的情况
        // 1、首次访问这个类的静态变量或静态方法时
        System.out.println(A.a);
        // 2、子类初始化,如果父类还没有初始化,会引发
        System.out.println(B.c);
        // 3、子类访问父类静态变量,只触发父类初始化
        System.out.println(B.a);
        // 4、会初始化类 B,并先初始化类 A
        Class.forName("com.yqj.B");
    }
}
 
class A {
    static int a = 0;
    static {
        System.out.println("a init");
    }
}
 
class B extends A {
    final static double b = 5.0;
    static boolean c = false;
    static {
        System.out.println("b init");
    }
}
上一篇 下一篇

猜你喜欢

热点阅读