今天巩固下JVM类(class)加载机制。在Java中一个类从加载进JVM内存直至卸载一共会经历过7个流程,分别为:加载(Loading),验证(Verification),准备( Preparation),解析(Resolution),初始化(Initialization),使用(Using),卸载(Unloading)。
加载 加载阶段共分三步:
1)通过类的全限定名来获取定义此类的二进制字节流。事实上,二进制字节流获取的方式非常灵活(例如:从ZIP包中读取,从网络中获取,通过动态代理技术以及通过JSP获取)。
2)将这个字节流所代表的静态存储结构转化为运行时数据结构。
3)在内存中生成一个代表这个类的 java.lang.Class对象,作为访问用的接口。
验证 验证是保证字节流中包含的信息严格符合当前虚拟机的要求,验证大致上能够分为4部分:
1)文件格式验证。这部分主要验证字节流的格式规范包括:是否以0xCAFEBABE开头;主、次版本号是否在当前虚拟机处理范围内;常量池中的常量是否有不支持的常量类型等。
2)元数据验证。这部分主要分析字节码描述的信息,已保证描述的信息符合Java语言规范包括。
3)字节码验证。这部分会对类的方法体进行校验。
4)符号引用验证
准备 准备阶段JVM会为类的静态变量(即static修饰的变量)分配内存及设置初始值,这些变量都将被分配到方法区中。而类中的实例变量会在对象实例化阶段随着对象一起分配到堆中。注意:这里JVM为变量初始化的值通常为数据类型的零值,但是在某些情况下比如对于用final修饰的变量,则在这一阶段直接初始化为对应的值。对应数据类型及初始值如下图:
解析 解析阶段,虚拟机会将常量池中的符号引用替换成直接引用。
符号引用 符号引用是以一组符号来描述所引用的目标。符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。各类虚拟机实现的内存布局可能不同但是能接受的符号引用都必须一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用 直接引用可以是直接指向目标的指针,相对偏移量或一个能间接定位到目标的句柄。直接引用与虚拟机实现的内存布局有关。同一个符号引用在不同虚拟机上转换的直接引用一般是不同的。同时,如果存在直接引用则引用的目标必定已存在在内存中。
初始化
在 Java中,虚拟机对于类的加载时机并未严格的约束而是根据各虚拟机的特性自行决定,但是对于初始化来说,存在几种情况使得类必须立即执行初始化操作:
1)当使用new,putstatic,getstatic,invokestatic这4条指令时,若该类未被初始化则需立即对该类进行初始化。
2)对类进行反射调用时,若该类未被初始化则需立即对该类进行初始化。
3)初始化一个类时,若该类存在父类且该父类未初始化则先初始化该类的父类。
4)虚拟机启动阶段,会初始化用户指定的需要执行的主类。
5)当使用Java 1.7的动态语言支持时,若一个 java.lang.invoke.MethodHandle实例最后的解析结果REF_putstatic,REF_getstatic,REF_invokestatic的方法句柄,若这个方法句柄对应的类未进行初始化则需对该类进行初始化。
上述5种情况会直接初始化对应类。