各自努力,最高处见!加油!
反射(三)类的加载
一、静态加载和动态加载
反射机制是java实现动态语言的关键,也就是通过反射实现动态加载。
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。new一个对象属于静态加载。
- 动态加载:运行时加载需要的类,如果运行时不用该类(即使该类不存在),则不报错,降低了依赖性。反射Class.forName(“类”)属于动态加载。
类加载的时机
- 当创建对象时(new)
- 当子类被加载时
- 调用类中的静态成员时
- 通过反射
二、类加载流程图
三、类加载的五个阶段
1、加载阶段(Loading)
JVM在该阶段的主要目的是将字节码从不同的数据源转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象。数据源包括:class文件,jar包,甚至网络
2、连接验证(Linking)
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 包括:文件格式验证(是否以魔数ox cafe babe开头)、元数据验证、字节码验证和符号引用验证
- 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
3、连接阶段-准备
JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量的内存都将在方法区中进行分配。
class A{
/***
* 对于类中的变量:属性、成员变量、字段这三种说法是等价的
* 1、n1是示例属性,不是静态变量,因此在准备阶段不会分配内存
* 2、n2是静态变量,准备阶段分配内存,但在准备阶段默认初始化为0,而不是这里的20.
* 3、n3是static final是常量,他和静态变量不一样,因为一旦赋值就不变,这里n3初始化为30
*/
public int n1=10;
public static int n2=20;
public static final int n3=30;
}
4、连接阶段-解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
5、初始化阶段(Initialization)
- 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程。
- ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
- 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类()方法,其他线程都需要阻塞等待,知道活动线程执行()方法完毕。
正是因为有了这个线程保护机制,才能保证某个类在内存中,只有一份Class对象
/**
* <clinit>(){//依次自动收集类中的所有**静态变量**的赋值动作和**静态代码块**中的语句,并进行合并
* System.out.println("B 中静态代码块被执行");
* num=300;
* num=100;
* }
* result:num=100
*/
public class ClassLoad03 {
public static void main(String[] args) {
System.out.println(B.num);//100 直接使用类的静态属性也会导致类的加载
}
}
class B{
static {
System.out.println("B 中静态代码块被执行");
num=300;
}
static int num=100;
public B() {
System.out.println("B类构造器被执行");
}
}