已加载但找不到入口点dllregisterserver_JVM笔记-类加载机制

本文详细介绍了JVM的类加载机制,包括加载、验证、准备、解析和初始化五个阶段,以及类加载器的工作原理,强调了双亲委派模型的重要性,并通过代码示例展示了如何自定义类加载器以及破坏双亲委派模型的情况。
摘要由CSDN通过智能技术生成

2ef7d28ffc836952d02dff2db5f28c08.png

JVM 不和包括 Java 在内的任何语言绑定,它只与 "Class文件" 这种特定的二进制文件格式所关联。而 Class 文件也并非只能通过 Java 源文件编译生成,可以通过如下途径而来:

59e9d1f15ebb5a1f2bd81047268ae181.png

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

Class 文件中描述的关于类的信息最终要加载到 JVM 中才能被运行和使用。

1. 类加载的时机

1.1 类的生命周期

一个类型(类或接口)从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期会经历加载(Loading)、验证(Verification)、准备(Prepare)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析统称为连接(Linking)。如图所示:

7789b6a0ff59765134e056653ef19e49.png

1.2 初始化时机

JVM 规范对于“加载”阶段并未强制约束。但对于“初始化”阶段,则规定有且仅有以下六种情况必须立即对其“初始化”:

  1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时。场景如下:
    1. 使用 new 关键字实例化对象;
    2. 读/写静态字段(static 修饰,无 final);
    3. 调用静态方法。
  2. 使用 java.lang.reflect 的方法对类型进行反射调用时。
  3. 初始化类时,若父类尚未初始化,需要先初始化其父类。
  4. 虚拟机启动时,需要先初始化用户指定的主类(main 方法所在类)。
  5. 使用 JDK 7 新加入的动态语言支持时,若一个 java.lang.invoke.MethodHandle 实例最后解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄,且该方法句柄对应的类未初始化,需要先初始化【平时似乎没用到过,暂不深究,以后有机会再分析】。
  6. 接口中定义了 JDK 8 加入的默认方法(default 修饰)时,在该接口的实现类初始化之前,需要先初始化这个接口。
注意:当一个“类”在初始化时,要求其父类全都已经初始化;但是,一个“接口”在初始化时,并不要求父接口全都初始化,只有真正使用到父接口时才会初始化(比如引用接口定义的常量)。

1.3 主动引用&被动引用

上述六种情况的行为称为对一个类型的“主动引用”,而除此之外的其他所有引用类型方式都不会触发初始化,称为“被动引用”。被动引用举例如下:

  • 示例代码
public class SuperClass {
    
    static {
    
        System.out.println("SuperClass init!");
    }

    public static int value = 123;
    public static final String HELLO_WORLD = "hello, world";
}

public class SubClass extends SuperClass {
    
    static {
    
        System.out.println("SubClass init!");
    }
}
PS: 为了跟踪类加载信息,可配置虚拟机参数 -XX:+TraceClassLoading
  • eg1
/**
 * 通过子类引用父类的静态字段,不会导致子类初始化
 */
public class NotInitialization {
    
    public static void main(String[] args) {
    
        System.out.println(SubClass.value);
    }
}

/* 类加载情况:SubClass 和 SuperClass 均被加载
 * 
 * 输出结果(父类初始化,子类未初始化):
 * SupClass init!
 * 123
 */
  • eg2
/**
 * 通过数组定义来引用类,不会触发此类的初始化
 */
public class NotInitialization {
    
    public static void main(String[] args) {
    
        SuperClass[] superClasses = new SuperClass[10];
    }
}

/* 类加载情况:SuperClass 被加载
 * 输出结果为空,SuperClass 未初始化 
 */
  • eg3
/**
 * 常量在【编译阶段】会存入调用类(NotInitialization)的常量池中,
 * 本质上并没有直接引用到定义常量的类,因此不会触发其初始化
 */
public class NotInitialization {
    
    public static void main(String[] args) {
    
        System.out.println(SuperClass.HELLO_WORLD);
    }
}

/* 类加载情况:SubClass 和 SuperClass 均未被加载
 *
 * 输出结果:
 * hello, world
 */

编译阶段通过常量传播优化,已将该常量的值("hello, world")直接存储在 NotInitialization 类的常量池中,以后 NotInitialization 对常量 SuperClass.HELLO_WORLD 的引用实际都被转化为对自身常量池的引用了。

PS: 其实 NotInitialization 类的 Class 文件中并不存在 SuperClass 类的符号引用入口,这两个类在编译成 Class 文件之后就没联系了。
<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值