🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。Java类的加载、连接和初始化都是在程序运行期间完成的,也就是动态加载和动态连接的。 # 类加载的时机 ![](https://img.kancloud.cn/72/1c/721c99a683916644e86d1ab6b3c6449f_487x162.png) 加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,解析则不一定,有时可以在初始化之后再开始。 Java虚拟机并没有规定什么时候一定要开始第一个阶段“加载”,但规定了有且仅有下面5中情况一定要进行“初始化”: 1、遇到new、getstatic、putstatic、invokestatic四个字节码指令时,生成这几个指令最常见的场景:new关键字实例化对象时、读取或设置一个类的静态字段时、调用一个类的静态方法时。 2、使用java.lang.reflect包的方法对类进行反射调用时,如果类没有进行过初始化,需要触发初始化 3、初始化一个类时,如果发现其父类还没初始化,需要先触发其父类的初始化 4、虚拟机启动时,需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类 5、使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析的结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法句柄对应的类没有进行过初始化,需要先触发其初始化 上面5中情况中的行为称为对一个类进行主动引用,除此之外其他引用类的方式不会触发类的初始化,称为被动引用。 # 类加载的过程 ## 加载 ## 验证 ## 准备 ## 解析 ## 初始化 # 类加载调用顺序 以下面示例进行测试: ```java /** * 控制台打印 */ class Log { public static String baseFieldInit() { System.out.println("Base Normal Field"); return ""; } public static String baseStaticFieldInit() { System.out.println("Base Static Field"); return ""; } public static String fieldInit() { System.out.println("Normal Field"); return ""; } public static String staticFieldInit() { System.out.println("Static Field"); return ""; } } /** * 基类 */ class Base { static { System.out.println("Base Static Block 1"); } private static String staticValue = Log.baseStaticFieldInit(); static { System.out.println("Base Static Block 2"); } { System.out.println("Base Normal Block 1"); } private String value = Log.baseFieldInit(); { System.out.println("Base Normal Block 2"); } Base() { System.out.println("Base Constructor"); } } /** * 派生类 */ class Derived extends Base { static { System.out.println("Static Block 1"); } private static String staticValue = Log.staticFieldInit(); static { System.out.println("Static Block 2"); } { System.out.println("Normal Block 1"); } private String value = Log.fieldInit(); { System.out.println("Normal Block 2"); } Derived() { System.out.println("Derived Constructor"); } /** * MAIN 主线程 */ public static void main(String[] args) { Derived d = new Derived(); } } ``` 打印结果如下: ```java Base Static Block 1 Base Static Field Base Static Block 2 Static Block 1 Static Field Static Block 2 Base Normal Block 1 Base Normal Field Base Normal Block 2 Base Constructor Normal Block 1 Normal Field Normal Block 2 Derived Constructor ``` 可以看到,执行顺序如下: 1、基类静态代码块、基类静态成员变量(优先级相同,按代码出现顺序依次执行)(只有第一次加载类时执行) 2、派生类静态代码块、派生类静态成员变量(优先级相同,按代码出现顺序依次执行)(只有第一次加载类时执行) 3、基类普通代码块、基类普通成员变量(优先级相同,按代码出现顺序依次执行) 4、基类构造函数 5、派生类普通代码块、派生类普通成员变量(优先级相同,按代码出现顺序依次执行) 6、派生类构造函数 参考文档:[https://www.zhihu.com/question/49196023](https://www.zhihu.com/question/49196023)