类的加载机制
类不被初始化的情况
- 对于静态字段,只有直接定义这个字段的类才会变初始化,子类引用父类的静态字段不会导致子类的初始化,只会初始化父类。
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
public class SubClass extends SuperClass {
public static int aa=32;
static {
System.out.println("SubClass init!");
}
}
- 数组的定义不会导致类的初始化
SuperClass[] sca = new SuperClass[10];
- 使用静态常量不会导致类的初始化,因为常量在编译阶段就会存入调用类的常量池,本质上没有引用到定义类的常量,类在被编译成class之后,静态常量和类就没有关系了。
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLOWORLD = "hello world";
}
public class Test {
@org.junit.Test
public void test() {
System.out.println(ConstClass.HELLOWORLD);
}
}
类加载过程7阶段
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这个顺序按部就班的执行。
何时会执行加载的第一个过程?
这么五种情况是肯定会执行的
- 在遇到new 、读取或者设置一个类的静态字段(final除外,final在编译期间就放入了常量池)的时候,以及调用一个类的静态方法的时候。
- 使用反射调用的时候,如何类没有被初始化则会先触发其初始化。
- 类没有没有初始化的时候,如果其父类没有初始化。则需要先触发其父类的初始化。
- 执行一个main()方法
-
当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
类的加载过程7个阶段
1. 加载
- 加载是类加载的过程的一个阶段,加载到我们的JVM需要需要经过三个过程
- 通过一个类名来获取到此类的二进制字节流
- 将字节流的静态存储结构转化为方法区运行时的数据结构
-
在内存中生成这个类的 Class对象,作为方法区的访问入口。
JVM加载类图示过程
2. 验证
- 验证这个步骤按照深入JAVA虚拟机一书描述的是,很重要的一个步骤。因为这个步骤需要保证,加载进来的Class文件是符合虚拟机规范的,并且不会对虚拟机造成安全危害,这个阶段只要有以下三个操作。
- 文件格式验证
这个步骤主要是检查字节流是否符合Class文件格式规范,是否以魔数0xCAFEBABE开头,版本号,常量问题。
下面是一个class得二进制字节码,cafe开头的就是魔数,它是JAVA之父自己定义的一个规则,CafeBabe代表咖啡宝贝,这个也符号Java的咖啡标志。
魔数
-
元数据验证
这个阶段只要是对字节码进行语义分析,以保证字节码符合Java语义的规范,可能包含的检查有这个class是否有父类,是否集成了不允许继承的类比如final修饰的类,是否实现了父类要求实现的接口,字段是否产生了矛盾。 -
字节码验证
这阶段是验证中最复杂的一步,主要是确定class的语义是否合法、符合逻辑,对类的方法体进行验证分析,保证类的方法不会对虚拟机安全造成危害。 -
符号引用验证
这个阶段主要是检查class的一些访问修饰符是否符合规范,符号引用验证的目的是确保解析动作能正常执行。
3. 准备
准备阶段是正式为类变量分配内存并设置类初始值的阶段,分配static修饰的变量,这些变量所使用的内存都将在方法区中分配。
public static int value = 123
value变量在被分配的时候,它的值不会是123而是默认的0。因为这个过程还未执行到Java代码,赋值为123的操作将在初始化阶段完成。
4. 解析
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,
- 符号引用以一组符号来描述所引用的目标,它可以是新形势的字面量。
- 直接引用是指向目标的指针、相对偏移量或者一个能直接定位到目标的句柄。
解析类型主要有
- 类接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
5. 初始化
类初始化是类加载过程中的最后一步,到了初始化阶段虚拟机才真正开始执行Java程序代码,准备阶段变量已经赋值过系统要求的初始值,而初始化阶段则回去初始化class的类变量和其他资源。
6. 使用
到了使用这一步就说明class已经正确的执行完了类加载的过程,class已经被加载到了虚拟机中,能被Java代码正确调用的过程。
7. 卸载
该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
- 加载该类的ClassLoader已经被GC。
- 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法