一、什么是类加载机制?
类加载就是JVM把类文件加载到内存里面,并对数据进行验证、准备、解析和初始化,最终形成能被JVM直接使用的java类型的过程。
二、类加载过程
加载——>链接(验证、准备、解析)——>初始化(主动引用、被动引用)——>使用——>卸载
三、过程详情
- 加载:把Java字节码转换为java.lang.Class对象
1)将class文件加载到内存中。
2)将静态数据结构转换为方法区运行时数据结构
3)在堆中生成代表这个类的java.lang.Class对象作为访问的入口,这个过程需要类加载器的参与。注意Class对象并不一定非得从Class文件中获取,既可以从压缩包中获取也可以在运行时计算生成(动态代理)。 - 链接:将Java类的二进制代码合并到Java运行状态中的过程。
1)验证:确保加载的类符合JVM规范和安全。
2)准备:为静态变量在方法区中分配空间并设置变量的初始值。
例如static int a = 3,在此阶段会在方法区中为a分配空间并把a初始化为0,将a赋值为3的putstatic指令是程序被编译后存放在类构造器client方法中的。但是如果声明为static final int a = 3,在编译阶段会为a生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性为a赋值为3。
3)解析:虚拟机将常量池的符号引用转变成直接引用。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。各种虚拟机的内存布局可以不同,但是它们能接受的符号引用一定是一致的。
直接引用:直接引用可以是指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用与虚拟机的内存布局相关,如果有了直接引用那么引用的目标一定加载到了内存。
- 初始化:初始化阶段是执行构造器client方法的过程。client方法是由编译器自动收集类中的静态变量和静态语句块中的语句合并而成的。虚拟机会保证client方法在多线程下是安全的。
注 :若类中没有静态属性或静态语句块那么编译器可以不为这个类生成client方法。
主动引用( 一定会发生类的初始化):
1、执行new, getstatic, putstatic, invokestatic四条字节码指令时
new:创建类实例的指令(与创建数组的指令不同——newarray, anewarray, multianewarray)
putstatic、getstatic:访问类字段的指令(staticz字段)。
invokestatic:调用类方法的指令(static方法)。
2、反射调用
3、父类未初始化
若父类未初始化则先初始化其父类
4、执行的主类
当虚拟机启动时用户需要指定一个执行的主类,虚拟机回先初始化这个主类。
5、使用jdk1.7的动态语言支持。
被动引用(不会发生类的初始化):
1、通过子类引用父类的静态字段,子类不会被初始化。
2、通过数组定义来引用类不会导致类的初始化。
3、final修饰的变量(常量)在编译阶段会调入类的常量池中,本质上没有直接引用到定义常量的类,因此该类也不会被初始化。
双亲委派模型:当一个类加载器收到类加载任务,会优先交给其父类加载器去完成,因此类加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时该类才会尝试执行加载任务。
双亲委派模型好处:保证类的唯一性。
- 使用:正常使用
- 卸载:GC把无用对象从内存中卸载。
四、案例
class Demo1{
public static void main(String[] args) {
A a = new A();
System.out.println(a.width);
}
}
class A{
public static int width = 100;
static{
System.out.println("静态初始化类A");
widht = 300;
}
public A(){
System.out.println("创建A的对象");
}
}
分析:
1) JVM加载Demo1的时候,首先在方法区生成Demo1类的静态数据,同时在堆里面也会生成java.lang.Class对象来代表类Demo1,通过该对象可以访问类的二进制结构。然后加载A类变量信息,同时也会在堆中生成对象a来表示类A。
2) main方法执行时会在栈里面生成main方法栈帧,一个方法对应一个栈帧。如果main方法调用了其他方法,那么会依次将其他方法压入栈。main方法里面有个局部变量A类型的a,一开始a的值为null,通过new调用A的构造器,栈里面生成构造方法A()的同时堆里面会生成A对象,然后把A对象的地址赋给栈中的a,此时a拥有A的地址。
3) 调用A.width时调用方法区数据。