静态加载 与 动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性强
- 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性(花费了额外的解析时间,也会造成效率的下降)
代码
public class Reflection {
public static void main(String[] args)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Scanner scanner = new Scanner(System.in);
int key = scanner.nextInt();
if (key == 1) {
// 静态加载:无论key是否为1,JVM都会加载Cat类
Cat cat = new Cat();
} else {
// 动态加载:只有key不是1的时候,JVM才会加载Dog类
Class clazz = Class.forName("com.suen.reflection.Car");
Dog dog = (Dog) clazz.newInstance();
}
}
}
类加载时机
- 创建对象【静态加载】
- 子类被加载,父类也被加载【静态加载】
- 调用类中的静态成员【静态加载】
- 反射【动态加载】
过程图
类加载各阶段解析
1. 加载
目的:JVM调用类加载器将字节码从不同的数据源(可能是class文件、jar包、网络)转化为二进制字节流加载到内存中( 方法区 二进制文件 + 堆中的Class对象)
类加载器使用了按照双亲委派机制进行加载
双亲委派模型的工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,因此所有的加载请求最终都会委派到启动类加载器中。只有父类加载器反馈自己无法加载这个类时,子类加载器才会尝试自己去加载。
优点:
- 可避免类的重复加载。
- 避免了java的核心API被篡改。
2. 链接
2.1 验证(vertification)
- 目的:确保Class文件的字节流中包含的信息 符合当前虚拟机 的要求,并且 不会危害虚拟机 自身的安全。
- 包括:文件格式验证(是否以 魔数ox cafe babe 开头)、元数据验证、字节码验证 和 符号引用验证
- 可以考虑使用 -Xverify:none 参数来 关闭大部分的类验证措施,缩短虚拟机类加载时间
2.2 准备(Preparation)
- JVM会在该阶段对 静态变量分配内存 并 默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。
- 这些变量使用的内存都将在 方法区 中进行分配
class A {
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
public int n1 = 10;
//2. n2 是静态变量,分配内存n2 是默认初始化0 ,而不是20
public static int n2 = 20;
//3. n3 是被final修饰的static字段,不会设置,因为final在编译的时候就分配了
public static final int n3 = 30;
}
2.3 解析(Resolution)
- 目的:是将常量池内的符号引用转换为直接引用的过程(将常量池内的符号引用解析成为实际引用)。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)
- 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义定位到目标即可。
- 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
3. 初始化
- 真正开始执行类中定义的Java程序代码,此阶段是执行
<clinit>()方法的过程 <clinit>()方法是 编译器 按 语句在源文件中出现的顺序 依次自动收集类中所有 静态变量 的赋值动作和 静态代码块 中的语句,并进行合并- 虚拟机 会保证一个类的
<clinit>()方法在多线程环境中被正确的加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕 - 调用类的静态属性会造成类的初始化