基本说明:
反射机制是JAVA实现动态语言的关键,也就是通过反射实现类动态加载。
- 静态加载:编译时加载的相关类,如果没有则报错,所以依赖性强。
- 动态加载:运行时加载所需要的类,运行时若不用该类则不报错。
类加载有三个阶段五个小步骤:
- 加载:JVM将字节码从数据源(class文件、jar包、网络)转换成二进制字节流读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器实现。(JVM自动完成)
- 连接:将类的二进制数据合并到JRE中。(JVM自动完成)
- 验证:对文件的安全进行校验。包括文件验证(是否以魔数开头)、元数据验证、字节码验证、符号引用验证。
可以通过设置Xverify : none
参数来设置关闭验证,缩短类加载的时间。 - 准备:对静态变量进行默认初始化(默认设置为值 0)并分配空间。若是static final 的值,因为是不变的常量,所以在这一个阶段JVM会直接赋值到它应该有的值。
- 解析:将常量池的符号引用转换成直接引用。简单的说就是把符号转换为相对应的地址引用。
- 验证:对文件的安全进行校验。包括文件验证(是否以魔数开头)、元数据验证、字节码验证、符号引用验证。
- 初始化:在初始化阶段才是真正开始类中定义的JAVA程序代码,是执行
<clinit>( )
方法的过程,在这个阶段才是程序员可以取控制的地方。<clinit>( )
方法:是由编译器按语句在源文件中出现的顺序,依次收集类中所有的静态变量
的赋值动作与静态代码块
中的语句,并进行合并。- 虚拟机会保证一个类的
<clinit>( )
方法在多线程环境中被正确的加锁、同步。如果多线程同时去初始化一个类,那么只会有一个线程去执行这个<clinit>( )
方法,其它的线程则会被阻塞。也正是因为有这个同步机制的存在,所以就算多线程去加载一个类,也不会产生多个CLass对象。
演示类加载的初始化
package practise4_Reflect;
/**
* 演示类加载的初始化
*/
public class ClassLoad01 {
public static void main(String[] args) {
//1. 加载 B类,并生成 B的Class对象
//2. 连接 num = 0
//3. 初始化阶段:依次收集类中所有的静态变量的赋值动作与静态代码块中的语句,并进行合并。
/* clinit(){
* System.out.println("B 静态代码块被执行");
// num = 500;
num = 100;
* }
*
* clinit发现num = 500会被覆盖,所以num = 500不执行。合并过后,num = 100,
*/
new B(); //会自动调用构造器的方法
System.out.println(B.num);
}
}
class B{
static {
System.out.println("B 静态代码块被执行");
num = 500;
}
static int num = 100;
public B() {
System.out.println("B 构造器被执行");
}
}