类加载
1. 基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时候加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用改类,即使不存在该类,则不报错,降低了依赖性
2. 类加载时机
- 当创建对象时(new),静态加载
- 当子类被加载时,父类也加载 ,静态加载
- 调用类中的静态成员时 ,静态加载
- 通过反射,动态加载
3. 类加载过程
3.1 加载阶段
1,JVM在该阶段主要目的是将字节码从不同的数据源(可以是class文件,也可能是jar包,甚至网络)转换成二进制字节流加载到内存中,并生成一个代表类的java.lang.Class对象
3.2 连接阶段-验证
1,目的是为了确保Class文件的字节流1这包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2,包括:文件格式验证(是否魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
3,可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
3.3 连接阶段-准备
1,jVM会在该阶段对静态变量,分配内存并默认初始化(对应的数据类型进行初始值,如0,0L,null…等)。这些变量所使用的内存都将在方法区进行分配
public static void main(String[] args) {
}
}
class A {
//属性-成员变量-字段
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20
//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
3.4 连接阶段-解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符 7 类符号引用进行。
符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量
3.5 初始化(Initialization)
1,到初始化阶段,才是真正的执行类中定义的java程序代码,此阶段是执行()方法的过程。
2,()方法是由编译器按语句在源文件中出现的顺序,依次自动1手机类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
3,虚拟机保证一个类的)()方法在多线程环境中被正确地加锁,同步,如果多个线程同时初始化一个类,那么只有一个线程去执行这个类()方法,其他的线程都需要阻塞等待,知道活动线程执行()方法完毕
public static void main(String[] args) throws ClassNotFoundException {
//1. 加载 B 类,并生成 B 的 class 对象
//2. 链接 num = 0
//3. 初始化阶段
// 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
/*
clinit() {
System.out.println("B 静态代码块被执行");
//num = 300;
num = 100;
}
合并: num = 100
*/
//new B();//类加载
//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载
//看看加载类的时候,是有同步机制控制
/*
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中, 只有一份 Class 对象
synchronized (getClassLoadingLock(name)) {
//.... }
}
*/
B b = new B();
}
}
class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B() {//构造器
System.out.println("B() 构造器被执行");
}
}