类加载三个阶段任务:
加载和连接阶段是JVM虚拟机完成的,无法进行控制。
初始化阶段是程序员可以控制的,例如在代码块中对静态成员初始化等。
1、加载阶段:
JVM在该阶段主要目的是
将字节码从不同的数据源(class文件,jar包,网络等)
转化为二进制字节流
加载到内存中(将某个类的字节码二进制数据加载到方法区域)
,
并生成一个代表该类的java.long.Class对象。
2、连接阶段:
连接—验证:
①目的是为了
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
②验证内容包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证。
③可以考虑使用 -Xverify:none 参数关闭大部分的类的验证措施,缩短虚拟机类加载时间。
连接—准备:
①JVM会在该阶段
对
静态变量
分配内存并
默认初始化(即int初始化为0,非基本数据类型为Null等)。
②这些变量所使用的
内存都将在
方法区
中进行分配。
普通变量准备阶段不分配内存。
静态变量准备阶段分配内存,赋值默认初始化值。
常量一旦赋值就不变,因此直接赋值到最终值。
连接—解析:
①虚拟机将常量池内的
符号引用替换成
直接引用的过程。
例:类A和类B具有引用关系。
但是在连接阶段类还没有真正加载,二者引用使用符号代替的
到了解析阶段,可以获得类由JVM分配的地址,此时将原来的符号替换成真正的引用。
3.初始化阶段
①到初始化阶段,才真正执行类中定义的Java程序代码,此阶段是执行<clinit>()方法的过程。
②<clinit>()方法是由编译器按语句在源文件中出现的
顺序,依次自动收集
类中所有的
静态变量的赋值动作和
静态代码块中的语句,并进行
合并。
例:使用类的静态属性(不进行new)也会导致类的加载。
类A定义和执行结果:
③虚拟机会保证一个类<clinit>()方法在多线程环境中被正确的加锁、同步。
如果多个线程同时区初始化一个类,那么只有一个线程区执行这个类的<clinit>()方法,
其他线程都需要阻塞等待,知道活动线程执行<clinit>()方法完毕。
这个机制,保证某个类在内存中只有一份Class对象。