龙哥网

龙哥网

Java类加载器探究(java类加载器原理)
2022-03-01

写在最前

Java 类加载器(ClassLoader)它是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。根据一个类的全限定名来读取此类的二进制字节流到 JVM 中,然后转换为一个与目标类对应的 java.lang.Class 对象实例。

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

类的生命周期

Java 类加载需要经历以下 7 个过程:

由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化

加载

加载是类加载的第一个过程,在这个阶段,将完成以下三件事情:

  1. 通过一个类的全限定名获取该类的二进制流。
  2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。
  3. 在内存中生成该类的 Class 对象,作为该类的数据访问入口。

验证

验证的目的是为了确保 Class 文件的字节流中的信息不回危害到虚拟机。在该阶段主要完成以下四种验证:

  1. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型。
  2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
  3. 字节码验证:整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
  4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的 内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。

/** * 在准备阶段 value 初始值为 0。 * 在初始化阶段才会变为 123 。 */
public static int value=123;

解析

该阶段主要完成符号引用到直接引用的转换动作。

初始化

到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。为类的静态变量赋予正确的初始值。

  • 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
  • 如果类中存在初始化语句,就依次执行这些初始化语句。

使用

new出对象程序中使用。

卸载

执行垃圾回收。

类加载器分类

JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。

启动类加载器

Bootstrap ClassLoader:负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME\jre\lib (JAVA_HOME 代表 JDK 的安装目录,下同) 文件夹中,如 rt.jar(runtime)、i18n.jar 等,这些是 Java 的核心类。这个 ClassLoader 比较特殊,它是由 c/c++ 代码实现的,我们将它称之为「根加载器」。

我们常用内置库 java.xxx. 都在 rt.jar 中,比如:java.util.、java.io.、java.nio.、java.lang. 等。

扩展类加载器

Extension ClassLoader:负责加载 JVM 扩展类,该加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 JAVA_HOME\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.* 开头的类),开发者可以直接使用扩展类加载器。

应用程序类加载器

Application ClassLoader:面向我们用户的加载器,该类加载器由 sun.misc.Launcher$AppClassLoader 实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。

自定义类加载器

因为系统的 ClassLoader 只会加载指定目录下的 class 文件,如果你想加载自己的 class 文件,那么就可以自定义一个ClassLoader。而且我们可以根据自己的需求,对 class 文件进行加密和解密。

双亲委派机制

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,子加载器才会尝试自己去加载。

模式优势

  • Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子类 ClassLoader 再加载一次。
  • 考虑到安全因素,Java 核心 Api 中定义类型不会被随意替换,假设通过网络传递一个名为 java.lang.Integer 的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心 Java API 发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的 java.lang.Integer,而直接返回已加载过的 Integer.class,这样便可以防止核心API库被随意篡改

打破双亲委托机制的案例

Java默认是双亲委托机制,当然开发中也有很多打破双亲行为的案例,比如:

  • Tomcat 可以加载自己目录下的 class 文件,并不会传递给父类的加载器。
  • Java 的 SPI,发起者是 BootstrapClassLoader,BootstrapClassLoader 已经是最上层的了。它直接获取了 AppClassLoader 进行驱动加载,和双亲委派是相反的。

JVM 中对象的创建过程

1、拿到内存创建指令

当虚拟机遇到内存创建的指令的时候(new 类名),来到了方法区,找根据 new 的参数在常量池中定位一个类的符号引用。

2、检查符号引用

检查该符号引用有没有被加载、解析和初始化过,如果没有则执行类加载过程,否则直接准备为新的对象分配内存。

3、分配内存

虚拟机为对象分配内存(堆)分配内存分为指针碰撞和空闲列表两种方式。

指针碰撞

所有的存储空间分为两部分,一部分是空闲,一部分是占用,需要分配空间的时候,只需要计算指针移动的长度即可。

空闲列表

虚拟机维护了一个空闲列表,需要分配空间的时候去查该空闲列表进行分配并对空闲列表做更新。可以看出,内存分配方式是由 Java堆是否规整决定的,Java 堆的规整是由垃圾回收机制来决定的。

4、初始化

分配完内存后要对对象的头(Object Header)进行初始化,这些信息包括:该对象对应类的元数据、该对象的GC代、对象的哈希码。

抽象数据类型默认初始化为 null,基本数据类型为0,布尔为 false…

5、调用对象的初始化方法

也就是执行构造方法。

免责声明
本站部分资源来源于互联网 如有侵权 请联系站长删除
龙哥网是优质的互联网科技创业资源_行业项目分享_网络知识引流变现方法的平台为广大网友提供学习互联网相关知识_内容变现的方法。