JVM实战-JVM之类加载时机

JVM实战-JVM之类加载时机

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。

关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(初始化必然要先加载)。

Java程序对类的使用分为主动引用和被动引用,针对上述六种情况,对于这六种会触发类型进行初始化的场景,《Java虚拟机规范》中使用了一个非常强烈的限定语——“有且只有”。

1 主动引用

主动引用的六种情况:

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
    • 使用new关键字实例化对象的时候。
    • 访问某个类或者接口的静态变量,或者对该静态变量进行赋值(被final修饰的静态变量、已在编译期间进入常量池的静态字段除外)。
    • 调用一个类型的静态方法的时候
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStaticREF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
  6. 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
/**
 * FileName: ClassInitTest
 * Author:   lxg
 */
public class ClassInitTest {
    public static void main(String[] args) {
        // 1. 创建实例
        InitTest1 it1 = new InitTest1();
        // 2. 访问静态属性
//        System.out.println(InitTest1.n);
        // 3. 调用静态方法
//        InitTest1.method();
        // 4. 反射
//        Class cls = Class.forName("classloder.initialization.InitTest1");
        // 5. 初始化子类
//        InitTest2 it2 = new InitTest2();
        // 6. 启动类触发初始化,执行main()触发
        // 7. 跳过
        // 8. 当一个接口中的默认方法(被default关键字修饰的接口方法)初始化时,IfTestImpl初始化时会触发InitTest1 it1 = new InitTest1(),证明IfTest接口初始化
//        IfTest ifTest = new IfTestImpl();
    }
}

// 类初始化会执行初始化类静态属性
class InitTest1 {
    static {
        System.out.println("InitTest1初始化...");
    }

    public static int n = 10; // 静态变量

    public static void method() { // 静态方法
        n = 30;
    }
}

class InitTest2 extends InitTest1 {
    static {
        System.out.println("InitTest2初始化...");
    }
}

// 接口,验证第8条
interface IfTest {
    InitTest1 it1 = new InitTest1();

    default void method() {
    }
}

class IfTestImpl implements IfTest {
    static {
        System.out.println("IfTestImpl初始化...");
    }
}

2 被动引用

除了上述主动引用的六种情况会触发类的初始化,其他的引用都不会触发初始化,被称为被动引用,以下是被动引用的其中几种情况举例:

  1. 通过子类的引用父类的静态属性,不会导致子类初始化。
  2. 通过定义数组引用类,不会导致该类初始化。
  3. 访问类中final修饰的静态变量(常量在编译期间会存入常量池,本质上并没有直接引用到定义该常量的类,所以不会触发该类的初始化)。
public class ClassInitTest2 {
    public static void main(String[] args) {
        System.out.println(SubClass.m);
    }
}

/**
 * 被动使用类字段
 * 通过子类引用父类的静态字段,不会导致子类的初始化
 */
class SuperClass{
    static {
        System.out.println("Super class init");
    }

    public final static int m = 20;
    public static int n = 10;
}

class SubClass extends SuperClass{
    static {
        System.out.println("SubClass init");
    }
}

image-20231114233108100

image-20231114233152695

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值