一、概述:
类加载是指将类从磁盘或网络读到 JVM 内存,然后交给执行引擎执行。
二、JAVA 类生命周期分析:
类的生命周期指的是从加载到卸载的基本过程,此过程包含 7 个阶段,如图:
说明: 一个已经加载的类被卸载的几率很小,至少被卸载的时间是不确定的,假
如需要卸载的话可使用System.exit(0);
三、类加载器执行过程分析:
1. 加载分析(loading):
我们知道类的加载过程中大致可分为加载、验证、准备、解析、初始化几大阶段,但这几个阶段的执行顺序又是怎样的呢?JVM 规范中是这样说的:
- 加载、验证、准备和初始化发生的顺序是确定的,而解析阶段则不一定;
- 加载、验证、准备和初始化这四个阶段按顺序开始不一定按顺序完成
加载基本步骤分析:
- 通过一个类的全限定名(类全名)来获取其定义的二进制字节流;
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构(方法区存储类结构);
- 在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口(堆中存储类字节码对象,然后引用方法区中的类结构).
加载路径分析:
JVM一般从如下三个地方加载类:
- JDK 类库中的类(lib\jar,lib\ext);
- 第三方类库中的类;
- 应用程序类库中的类;
自定义类加载器可以自定义类加载路径:
- 可以自定义类加载的目录加载类;
- 可以从网络加载类;
3. 类加载方式及时机分析:
JVM 中的类加载方式主要两种:
- 隐式加载
- 显式加载
隐式加载(即非主动加载):
- 访问类的静态成员(例如类变量,静态方法);
- 构建类的实例对象(例如使用 new 关键字构建对象或反射构建对象);
- 构建子类实例对象(构建类的对象时首先会加载父类类型)
显式加载(通过类加载器等方式主动加载类):
- ClassLoader.loadClass(…)
- Class.forName(…)
说明 :
- 通过 ClassLoader 对象的 loadClass(…) 方法加载类不会执行静态代码块,使用Class.forName(…) 可以选择是否执行静态代码块;
- 可通过指定运行参数
-XX:+TraceClassLoading
,查看类的加载顺序
2. 连接分析 (linking)
2.1 验证(Verification):
这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 验证阶段大致会完成4 个阶段的检验动作:
- 文件格式的验证;
- 元数据验证;
- 字节码合法性验证;
- 符 号 引 用 验 证 (Class 文 件 中 以 CONSTANT_Class_info、CONSTANT_Fieldref_info 等常量形式出现)
说明:
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverify:none
参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
2.2 准备(Preparation)
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
- 类变量(static)内存分配;
- 按类型进行初始默认值分配(如 0、0L、null、false 等)
例如:
假设一个类变量的定义为:public static int value = 3;
那么变量 value 在准备阶段过后的初始值为 0,而不是 3,把 value 赋值为 3 的动作将在初始化阶段才会执行
- 如果类字段的字段属性表中存在 ConstantValue 属性,即同时被 final 和 static 修饰,那么在准备阶段变量value 就会被初始化为 ConstValue 属性所指定的值。
例如:
假设上面的类变量 value 被定义为: public static final int value = 3;
编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 3
2.3. 解析(Resolution)
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,其中:
- 符号引用: 就是一组符号(例如 CONSTANT_Fieldref_info)来描述目标,可以是任何字面量;
- 直接引用: 就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄;
说明:
相同的符号引用不同 JVM 机器上对应的直接引用可能不同,直接引用一般对应已加载到内存中的一个具体对象。
3. 初始化分析(Initialization)
此阶段为类加载的最后一个阶段,这个阶段我们让自定义类加载器参与进来,其余阶段完全由 JVM 主导。例如 JVM 负责对类进行初始化,主要对类变量进行初始化。
在 Java 中,对类变量进行初始值的设定有两种方式:
- 声明类变量时指定初始值;
- 使用静态代码块为类变量指定初始值
说明:只有当对类的主动使用的时候才会导致类的初始化
class A{
public static int a=10; static {
System.out.println("A.a="+a);
}
} class B extends A{ static {
System.out.println("B");
}
}
public class TestClassLoader03 { public static void main(String[] args) {
System.out.println(B.a);
}
}
说明: 当通过 B 对象访问 A 类的 a 属性时不会执行 B 类的静态代码块