虚拟机类加载机制
因为疫情原因,都从去年颓废到今年了,这可不行,得抓紧了,还要暴富呢!!!
今年让我们先从Java虚拟机类的加载机制开始
1.1 生命周期
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期将会经历
七个阶段,其中验证,准备,解析,三个部分统称为连接。
其中只有解析阶段不一定按顺序执行,它在某些情况下可以在初始化阶段之后开始。
1.2 什么情况下开始 “ 初始化阶段 ”
关于开始第一个阶段 “ 加载阶段 ”,这点交给虚拟机的具体实现来自由把握。
对于 “ 初始化阶段 ”,严格规定有且只有六种情况必须立即对类进行初始化(加载,验证,准备自然需要在此之前开始)。
- 遇到new、getstatic、putstatic 或 invokestatic 这四条字节码指令时,如果类型没有进行过初始化,则需要触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
- 使用 new 关键字实例化对象的时候
- 读取或者设置一个类型的静态字段(被 final 修饰,已经在编译期把结果放入常量池的静态字段除外)的时候
- 调用一个类型的静态方法的时候
- 使用 java.lang.reflect 包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类
- 当时用JDK 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化
- 当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
这六种场景中的行为称之为对一个类型进行的主动引用,除此之外所有引用类型的方式都不会出发初始化,称为被动引用。
1.3 代码举例
三个被动引用的例子
package Month1.Day0101;
/**
* 三个被动引用的例子
*/
class SuperClass{
static {
System.out.println("SuperClass init !");
}
public static int value = 123;
}
class SubClass extends SuperClass{
static {
System.out.println("SubClass init !");
}
}
/**
* 被动使用类字段演示一
* 通过子类引用父类的静态字段,不会导致子类初始化
* */
public class NotInitialization1{
/**
* 运行结果如下,没有输出SubClass init !说明子类没有进行初始化
* SuperClass init !
* 123
* */
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
/**
* 被动使用类字段演示二
* 通过数组定义来引用类,不会触发此类的初始化。
* */
class NotInitialization2 {
/**
* 运行后没有输出“ SubClass init ! ”说明没有触发 SubClass 类的初始化阶段
* */
public static void main(String[] args) {
SubClass[] sca = new SubClass[10];
}
}
class ConstClass{
static {
System.out.println("ConstClass init !");
}
public static final String HELLOWORLD = "hello world";
}
/**
* 被动使用类字段演示三
* 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
* */
class NotInitialization3 {
/**
* 运行后没有输出“ ConstClass init ! ”说明没有触发 ConstClass 类的初始化阶段
* */
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);
}
}
前四个主动引用的例子
package Month1.Day0101;
/**
* 四个主动引用的例子
*/
class TestClass{
static {
System.out.println("TestClass init !");
}
public static String HELLOWORLD = "hello world";
static void say(){
System.out.println("static function!");
}
}
/**
* 一。遇到new、getstatic、putstatic 或 invokestatic 这四条字节码指令时,如果类型没有进行过初始化,则需要触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
* 1.使用 new 关键字实例化对象的时候
* 2.读取或者设置一个类型的静态字段(被 final 修饰,已经在编译期把结果放入常量池的静态字段除外)的时候
* 3.调用一个类型的静态方法的时候
* */
class Initialization1_1 {
/**
* 运行结果为TestClass init !,说明进行了初始化
* */
public static void main(String[] args) {
TestClass testClass = new TestClass();
}
}
class Initialization1_2 {
/**
* 运行结果如下,说明进行了初始化
* TestClass init !
* hello world
* */
public static void main(String[] args) {
System.out.println(TestClass.HELLOWORLD);
}
}
class Initialization1_3 {
/**
* 运行结果如下,说明进行了初始化
* TestClass init !
* static function!
* */
public static void main(String[] args) {
TestClass.say();
}
}
/**
* 二。使用 java.lang.reflect 包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化
* */
class Initialization2{
/**
* 运行结果如下,说明进行了初始化
* TestClass init !
* */
public static void main(String[] args) throws ClassNotFoundException {
Class cls = Class.forName("Month1.Day0101.TestClass");
}
}
/**
* 三。当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
* */
class FatherClass{
static{
System.out.println("FatherClass init !");
}
}
class Initialization3 extends FatherClass{
/**
* 运行结果如下,说明进行了初始化
* FatherClass init !
* Initialization3
* */
public static void main(String[] args) {
System.out.println("Initialization3");
}
}
/**
* 四。当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类
* */
class Initialization4 {
static{
System.out.println("Initialization4 init !");
}
/**
* 运行结果如下,说明进行了初始化
* Initialization4 init !
* Initialization4
* */
public static void main(String[] args) {
System.out.println("Initialization4");
}
}