1. 加载的时机 (加载阶段虚拟机需要完成3件事)
a. 通过一个类的全限定名来获取定义此类的二进制字节流
b. 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
c.在内存中生成一个代表这个类的java.lang.Class对象,作业方法区这个类的各种数据的访问入口
2.验证
a.文件格式验证
这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证之后,这段字节流才被允许进人 Java 虚拟机内存的方法区中进行存储,所以后面 的三个验证阶段全部是基于方法区的存储结构(内存)上进行的,不会再直接读取、操作字节流了。
b.元数据验证
元数据验证是验证的第二阶段,主要目的是对类的元数据信息进行语义校验,保证不存在与《Java 语言规范》定义相悖的元数据信息。
c.字节码验证
如果一个类型中有方法体的字节码没有通过字节码验证,那它肯定是有问题的
d.符号引用验证
符号引用验证的主要目的是确保解析行为能正常执行,如果无法通过符号引用验证,将会抛出异常。
e.验证(总结)
验证阶段对于虚拟机的类加载机制来说,是一个非常重要的、 但却不是必须要执行的阶段,因为验证阶段只有通过或者不通过的差别,只要通过了验证,(包括自己编写的、第三方包中的、从外部加载的、动态生成的等所有代码)都已经被反复 使用和验证过,在生产环境的实施阶段就可以考虑使用-Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。其后就对程序运行期没有任何影响了。如果程序运行的全部代码
初始化
初始化主要是对一个 class 中的 static{}语句进行操作(对应字节码就是 clinit 方法)。 <clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit >()方法。 初始化阶段,虚拟机规范则是严格规定了有且只有 6 种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
-
遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这 4 条指令的最常见的 Java 代码场景是:
- 使用 new 关键字实例化对象的时候。
- 读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候
- 调用一个类的静态方法的时候。
-
使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
-
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
-
当虚拟机启动时,用户需要指定一个要执行的主类(包含 main()方法的那个类),虚拟机会先初始化这个主类。
-
当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法 句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
-
当一个接口中定义了 JDK1.8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那该接口要在其之前 被初始化。
测试案例
/**
* 父类
*/
public class SuperClazz {
static {
System.out.println("SuperClass init!");
}
public static int value=123;
public static final String HELLOWORLD="hello king";
public static final int WHAT = value;
}
/**
* 子类
*/
public class SubClaszz extends SuperClazz {
static{
System.out.println("SubClass init!");
}
}
/**
*初始化的各种场景
* 通过VM参数可以观察操作是否会导致子类的加载 -XX:+TraceClassLoading
**/
public class aaa {
public static void main(String[]args){
Initialization initialization = new Initialization();
initialization.M1();//打印子类的静态字段
initialization.M2();//使用数组的方式创建
initialization.M3();//打印一个常量
initialization.M4();//如果使用常量去引用另外一个常量
}
public void M1(){
System.out.println(SubClaszz.value);
}
public void M2(){
SuperClazz[]sca = new SuperClazz[10];
}
public void M3(){
System.out.println(SuperClazz.HELLOWORLD);
}
public void M4(){
System.out.println(SuperClazz.WHAT);
}
}
1、调用M1方法
运行结果 对应6 种情况必须立即对类进行“初始化”中的a c
结果显示:父类子类被加载,但只有父类被初始化(static运行),子类并未被初始化(static未运行)
2、调用M2方法
运行结果
结果显示:使用数组的方式, 不会触发初始化(触发父类加载,不会触发子类加载)
3、调用M3
运行结果
结果显示:打印一个final修饰的常量,不会触发初始化(同样不会触类加载、编译的时候这个常量已经进入了自己class的常量池)
4、调用M4
运行结果
结果显示:如果使用常量去引用另外一个不加final修饰的常量,会导致类的初始化