文章目录
一、概述
类的加载机制
: 虚拟机把描述类的数据从Class文件
加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。Class文件
: 指一串二进制的字节流(无论以何种形式存在),而非特指某个存在于具体磁盘中的文件。
关联文章:
-
《深入理解Java虚拟机》
二、类的加载流程
类从被加载到虚拟机内存开始,到卸载出内存为止的完整周期如下图所示:
类加载阶段的注意事项:
加载、验证、准备、初始化、卸载
5个阶段的顺序是确定的;但是解析
阶段可能是在初始化
阶段之前或之后,其目的是为了支持 Java 的动态绑定。加载
阶段要根据虚拟机具体实现来确定,不同虚拟机可能存在差异。初始化
阶段有且只有
以下5中情况。- 遇到
new、getstatic、putstatic、invokestatic
这4条字节码指令时,如果类没有进行初始化,则需要先触发其初始化。 - 使用
java.lang.reflect
包的方法对类进行反射调用,如果类没有初始化,则需要先触发其初始化。 - 当初始化一个类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化(这个只针对于类,接口在初始化时,不要求其父接口全部完成初始化,只有在真正使用到父接口的时候,才会初始化)。
- 虚拟机启动时,会调用包含了
main()
方法的那个类。 - 使用JDK1.7的动态语言时,如果一个
java.lang.invoke.MethodHandle
实例最后解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic
的方句柄,且这个方法所对应的类没有进行初始化,则需要先触发其初始化。
- 遇到
三、类加载的过程
下面详细讲解一下类加载的前5个阶段,即 加载阶段、验证阶段、准备阶段、解析阶段、初始化阶段
;
3.1 加载阶段
在加载阶段,虚拟机完成了3件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流缩代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的
java.lang.Class
对象(该对象存储于方法区中,而不是堆内存中),作为方法区这个类的各种数据的访问入口。
3.2 验证阶段
验证的目的:
为了确保 Class 文件的字节流中包含的信息,符合当前虚拟机的要求,且不会危害虚拟机自身的安全。
验证的内容: 文件格式验证、元数据验证、字节码验证、符号引用验证
。
-
文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理;验证之后,字节流才会进入到内存的方法区中进行存储。
-
元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
-
字节码验证
通过数据流和控制流的分析,确定程序语义是合法的、符合逻辑的。
-
符号引用验证
在虚拟机将符号引用转化为直接引用时进行,这个转化的动作在连接的第三阶段(即解析阶段)发生。目的是确保解析动作能正常执行。
3.3 准备阶段
准备阶段是正式为 类变量(被static修饰的变量)
分配内存并设置变量 初始值
的阶段,这些变量所使用的内存都将在 方法区
中进行分配。
类变量: 被 static
修饰的变量。
初始值: 通常情况下,变量的数值为零值;特殊情况下(如常量),在准备阶段变量就为指定的值。
3.4 解析阶段
虚拟机将常量池内的符号引用替换为直接引用。
符号引用: 以一组符号来描述所引用的目标,符号可以是任何形式的字面量,能定位到目标即可。
直接引用: 可以直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。
3.5 初始化阶段
初始化阶段是真正开始执行类中定义的Java程序代码。
在 加载阶段、验证阶段、准备阶段、解析阶段、初始化阶段
这几个阶段中,除了加载阶段用户可以自定义类的加载器(可操作性强)之外,其余阶段都是由虚拟机主导和控制。
四、类加载器(双亲委派模型)
ClassLoader 即我们常说的类加载器,其功能是用于从Class文件加载所需的类,其中系统提供3种的类加载器,如下图所示:
4.1 类加载器
Bootstrap ClassLoader
启动类加载器,一般由C++实现,是虚拟机的一部分。该类加载器主要加载 <JAVA_HOME>\lib 目录中能被虚拟机识别的类库(按文件名识别,如rt.jar)。启动类加载器无法被Java程序直接引用。
Extension ClassLoader
扩展类加载器,由Java实现,独立于虚拟机的外部。该加载器是由sun.misc.Launcher$ExtClassLoader实现。主要职责将 <JAVA_HOME>\lib\ext 目录中的所有类库,开发者可直接使用扩展类加载器。
Application ClassLoader
应用程序类加载器,该加载器是由sun.misc.Launcher$AppClassLoader实现,该类加载器负责加载用户类路径上所指定的类库。开发者可通过ClassLoader.getSystemClassLoader()方法直接获取,故又称为系统类加载器。当应用程序没有自定义类加载器时,默认采用该类加载器。
4.2 双亲委派模型
ClassLoader 的双亲委派模型中,各个ClassLoader之间的关系是通过组合关系(而非继承关系)来复用父加载器。
加载流程:
当一个 ClassLoader 收到来类加载的请求,首先把该请求委派该父类 ClassLoader 处理,如果父类 ClassLoader 无法处理,则由当前类 ClassLoader 来处理(即父类优先于子类处理加载请求)。
类加载器的查找顺序为:启动类加载器 -> 扩展类加载器 -> 系统类加载器。
加载流程如下图所示。
优点: 保证了程序的安全性。
已知 java.lang.Object 类是在 rt.jar 中,是由Bootstrap ClassLoader 类加载器加载。当我们想用自己编写的 java.lang.Object 类替换系统的 java.lang.Object 类时,通过 ClassLoader 的双亲委派特性可知最终会让让最上层的 Bootstrap ClassLoader 进行加载,而 Bootstrap ClassLoader 已经加载过 rt.jar 中的 java.lang.Object 类,所以就不会再加载我们自定义的java.lang.Object 类,从而保证了程序的安全。