一、概述
虚拟机把描述类的 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
- 在 Java 中,类型的加载、连接和初始化过程都是在程序运行期间完成的。
- 这种策略虽然会在类加载的时候增加一些性能开销,但是为 Java 程序提供了高度的灵活性。
二、类加载的时机
类从被加载到虚拟机内存中开始,到卸载出内存位置,整个生命周期包括:
- 加载 Loading
- 验证 Verification
- 准备 Preparation
- 解析 Resolution
- 初始化 Initialization
- 使用 Using
- 卸载 Unloading
当下面五种情况时,必须进行初始化:
- 遇到
new、getstatic、putstatic 或invokestatic
指令时:new 关键字实例化对象,getset 一个静态类的字段,调用一个静态类的方法时
-
使用
java.lang.reflect
包的方法对类进行反射调用的时候 -
一个类的父类没有被初始化时
-
虚拟机启动时,需要先初始化包含 main()方法的那个类
-
jdk1.7 methodHandle实例初始化方法句柄所对应的类
三、类加载的过程
1、加载
1)通过一个类的全限定类名来获取定义此类的二进制字节流
比如:从ZIP 包中获取,JAR、WAR 等,从网络中获取,从动态代理运行时计算生成
2)将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
3)在内存中生成一个代表这个类的 java.lang.class对象,作为方法区这个类的各种数据的访问入口
加载阶段和连接阶段的部分内容是交叉进行的。
2、验证
为了防止字节流的信息危害虚拟机的安全,需要对字节流进行验证。
1)文件格式验证
验证字节流是否符合 class 文件的规范,目的是保证字节流能正确的解析并存储在方法区之内。后面三个阶段都是在方法区进行操作
2)元数据验证
对字节码描述的信息进行语义验证,保证符合 java语言规范。
3)字节码验证
对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
4)符号引用验证
在虚拟机将符号引用转换为直接引用的时候,进行验证。比如在指定类中是否存在符合方法的字段描述。
3、准备
正式为类变量(static 修饰的)分配内存并设置初始值的阶段。
public static int a =123;
//准备阶段过后 a=0,初始化阶段才会赋值 123
public static final int a =123;
//为 a 生成constantValue 属性,在准备阶段就赋值为 123
4、解析
解析是虚拟机将常量池内的符号引用替换为直接引用的过程。
5、初始化
初始化是执行类构造器<clinit>()
方法的过程。
<clinit>()
方法是由所有类变量的赋值动作和静态语句块中的语句合并产生的- 父类的
<clinit>()
必须先执行,但是子类的<clinit>()
不需要显式的调用父类的构造器 - 如果一个雷总没有静态语句块或者对变量的复制操作,那么编译器可以不为这个类生成
<clinit>()
方法 - 父类接口的
<clinit>()
方法不需要先执行,除非父类接口中定义的变量使用时 - 只有一个线程会执行这个类的
<clinit>()
方法,其他线程都会阻塞知道<clinit>()
执行结束
四、类加载器
通过一个类的全限定名来获取描述此类的二进制字节流的代码,叫做类加载器
1、类与类加载器
如果同一个 class 文件被两个不同的类加载器加载,那么这就是两个不同的类
2、双亲委派模型
双亲委派模型:一个类加载器会先将加载请求委派给父类,层层递推。当父加载器无法完成这个加载请求的时候,子加载器才会尝试自己去加载。
这样可以避免系统基础代码出现混乱。
- 启动类加载器(Bootstrap ClassLoader):负责加载
<JAVA_HOME>\lib
路径下,或者是-Xbootclasspath指定路径的类库。该加载器无法被 java 直接引用 - 扩展类加载器(Extension ClassLoader):负责加载
<JAVA_HOME>\lib\ext
目录中的,或者被 java.ext.dirs 指定的路径中的类库 - 应用类加载器(Application ClassLoader):是系统类加载器,负责加载用户路径上指定的类,是程序中默认的类加载器
.