1、类加载过程详解
1.1、类加载过程详解图
1.2、类生命周期
- 一个类从被加载到虚拟机内存中开始,到卸载出内存为止,他的生命周期将会经历以下七个阶段。
需要说明的是:
加载
-->验证
-->准备
-->初始化
-->卸载
,这五个阶段的顺序是确定的,类的加载过程必须按该顺序开始执行(只要求按顺序开始,不要求前一个结束了,下一个再开始)。这五个阶段通常是相互交叉地混合进行,会在一个阶段执行的过程中调用、激活另一个阶段。解析
阶段在某些情况下可以在初始化阶段之后开始。
1.3、类生命周期各阶段详解说明
加载:
|_ 通过一个类的全限定名,在硬盘中查找并通过IO 加载类的二进制数据(class文件)
|_ 转为方法区数据结构,并存放到方法区。方法区:类的类信息。
|_ 在 Java 堆中产生 java.lang.Class 对象。堆:Class 文件对应的类实例。
验证: 作用为验证 class 文件是否符合规范。
|_ 文件格式的验证:
|_ 是否以 0xCAFEBABE 开头。
|_ 版本号是否合理。
|_ 元数据验证:
|_ 是否有父类。
|_ 是否继承了 final 类(final 类不能被继承,如果继承了就有问题)
|_ 非抽象类实现了所有抽象方法。
|_ 字节码验证:
|_ 运行检查。
|_ 栈数据类型和操作码操作参数吻合(例如栈空间只有2字节,但其实却需要大于2字节,此时就认为这个字节码是有问题的)
|_ 跳转指令指向合理的位置。
|_ 符合引用验证:
|_ 常量池中描述类是否存在。
|_ 访问的方法或字段是否存在且有足够的权限。
|_ 说明:可使用 -Xverify:none 关闭验证。
准备:
|_ 作用:为类的静态变量进行初始化,并为类的静态变量分配内存空间,并赋予初始值。
|_ final static 修饰的变量:直接赋值为用户定义的值,例如:
|_ private final static int value = 123,直接赋值 123.
|_ private static int value = 123,该阶段的值依然是 0.
解析: 是将符号引用转换为直接引用.
初始化: JVM 对类进行初始化,对静态变量赋予正确值.
|_ 执行<clinit>方法,clinit 方法由编译器自动收集类里面的所有静态变量的赋值动作及静态语句块合并而成,也叫类构造器方法。
|_ 初始化的顺序和源文件中的顺序一致。
|_ 子类的<clinit>被调用前,会先调用父类的<clinit>方法。
|_ JVM 会保证 clinit 方法的线程安全性。
|_ 初始化时,如果实例化一个新对象,会调用<init>方法对实例变量进行初始化,并执行对应的构造方法内的代码。
|_ 构造块在构造方法之前执行。
2、类加载器
类加载器只用于实现类的加载动作,对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确定其在 Java 虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间。
- 类加载器由高级别到低级别依次为:
-
- |_ (负责加载 JDK/JRE/LIB目录下,或者被-Xbootclasspath参数所指定的路径中存放的,能被识别的类库,到虚拟机的内存中)
- |_ (负责加载 JDK/JRE/LIB/EXT 目录下,或者被java.ext.dirs系统变量所指定的路径中所有的类库)
- |_ 负责加载用户类路径 (ClassPath)上所有的类库,开发者可以直接在代码中使用这个类加载器
- |_ 流、网络、数据库
BootStrapClassLoader(启动类加载器—C语言写的)
ExtClassLoader(扩展类加载器)
AppClassLoader(应用程序类加载器)
用户自定义类加载器
3、双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
- 双亲委派模型的实现源码如下:
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先,检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载时
// 再调用本身的findClass方法来进行类加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
从源码可以看出:先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的 loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败, 抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载。
.