JVM之 用例子讲解JVM虚拟机的类加载机制

虚拟机类加载机制

因为疫情原因,都从去年颓废到今年了,这可不行,得抓紧了,还要暴富呢!!!

今年让我们先从Java虚拟机类的加载机制开始

1.1 生命周期

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期将会经历

七个阶段,其中验证,准备,解析,三个部分统称为连接。

其中只有解析阶段不一定按顺序执行,它在某些情况下可以在初始化阶段之后开始。

1.2 什么情况下开始 “ 初始化阶段 ”

关于开始第一个阶段 “ 加载阶段 ”,这点交给虚拟机的具体实现来自由把握。

对于 “ 初始化阶段 ”,严格规定有且只有六种情况必须立即对类进行初始化(加载,验证,准备自然需要在此之前开始)。

  1. 遇到new、getstatic、putstatic 或 invokestatic 这四条字节码指令时,如果类型没有进行过初始化,则需要触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
    1. 使用 new 关键字实例化对象的时候
    2. 读取或者设置一个类型的静态字段(被 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 代码举例

三个被动引用的例子

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");
    }
}


猜你喜欢

转载自blog.csdn.net/u013523775/article/details/112061113
今日推荐