Java ClassLoader

4 篇文章 0 订阅

1. 类加载机制

Java 中的源码 .java 文件会在运行前被编译成 .class 文件,文件内的字节码的本质就是一个『字节数组』,它有特定的复杂的内部格式,Java 类初始化的时候会调用 java.lang.ClassLoader 加载字节码,.class 文件中保存着 Java 代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的 .class 文件,并创建对应的 class 对象,将 class 文件加载到虚拟机的内存,而在 JVM 中类的查找与装载就是由 ClassLoader 完成的,而程序在启动的时候,并不会一次性加载程序所要用的所有 class 文件,而是根据程序的需要,来动态加载某个 class 文件到内存当中的,从而只有 class 文件被载入到了内存之后,才能被其它 class 所引用。所以 ClassLoader 就是用来动态加载 class 文件到内存当中用的。

2. 类加载方式

Java 类加载方式分为『显式』和『隐式』:

  • 显式:利用反射来加载一个类

  • 隐式:通过 ClassLoader 来动态加载,new 一个类 或者 类名.方法名 返回一个类

@Test
public void loadClassTest() throws Exception {
    // 1. 反射加载
    Class<?> aClass = Class.forName("java.lang.Runtime");
    System.out.println(aClass.getName());

    // 2. ClassLoader 加载
    Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("java.lang.ProcessBuilder");
    System.out.println(aClass1.getName());
}

那也就是其实可以通过 ClassLoader.loadClass() 代替 Class.forName() 来获取某个类的class对象。

3. ClassLoader

ClassLoader(类加载器)主要作用就是将 class 文件读入内存,并为之生成对应的 java.lang.Class 对象。

JVM 中存在 3 个内置 ClassLoader:

  1. BootstrapClassLoader 启动类加载器

负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.* 、java.io.* 、java.nio.* 、java.lang.* 等等。

  1. ExtensionClassLoader 扩展类加载器

负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中。

  1. AppClassLoader 系统类加载器

它才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

除了 Java 自带的 ClassLoader 外,还可以自定义 ClassLoader,自定义的 ClassLoader 都必须直接或间接继承自 java.lang.ClassLoader 类 (你可以继承 ExtensionClassLoader 和 AppClassLoader) 。

需要主机的是,Bootstrap ClassLoader 并不继承自 ClassLoader ,因为它不是一个普通的 Java 类,底层由 C++ 编写,已嵌入到了 JVM 内核当中,当 JVM 启动后,Bootstrap ClassLoader 也随着启动,负责加载完核心类库后,并构造 Extension ClassLoader 和 App ClassLoader 类加载器。

4. 类加载流程

类加载指的是在 .java 文件编译成 .class 字节码文件后,当需要使用某个类时,虚拟机将会加载它的 .class 文件,将 .class 文件读入内存,并在内存中为之创建一个 java.lang.Class 对象。但是实现步骤看起来会比较空洞和概念化,暂时不去深入研究,理解类加载是做什么的并了解加载过程即可。后续有刚需再去深入。

类加载大致分为三个步骤:加载、连接、初始化。

a. 加载

类加载指的是将 .class 文件读入内存,并为之创建一个 java.lang.Class 对象,即程序中使用任何类时,也就是任何类在加载进内存时,系统都会为之建立一个 java.lang.Class 对象,这个 Class 对象包含了该类的所有信息,如 Filed,Method 等,系统中所有的类都是 java.lang.Class 的实例。

类的加载由类加载器完成,JVM 提供的类加载器叫做系统类加载器 (上述三个) ,此外还可以通过自定义类加载器加载。

通常可以用如下几种方式加载类的二进制数据:

  • 从本地文件系统加载 .class 文件。

  • 从JAR包中加载 .class 文件,如 JAR 包的数据库启驱动类。

  • 通过网络加载 .class 文件。

  • 把一个 Java 源文件动态编译并执行加载。

b. 链接

链接阶段负责把类的二进制数据合并到 JRE 中,其又可分为如下三个阶段:

  1. 验证:确保加载的类信息符合 JVM 规范,无安全方面的问题。

  1. 准备:为类的静态 Field 分配内存,并设置初始值。

  1. 解析:将类的二进制数据中的符号引用替换成直接引用。

c. 初始化

类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

5. 双亲委派机制基本概念

前面提到了 Java 自带 3 个 ClassLoader,包括我们也可以实现自定义 ClassLoader 完成类加载,但是具体某个类的加载用的是哪个 ClassLoader 呢。这里涉及到一个『双亲委派』机制。

下图基本概述了双亲委派机制(先走蓝色箭头再走红色箭头)

双亲委派简单理解:向上委派,向下加载。这个过程非常类似于递归,分为『递』和『归』两个环节。

当一个 .class 文件要被加载时:不考虑我们自定义类加载器,

  • 首先会在 AppClassLoader 中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的 loadClass 方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。直到到达 Bootstrap classLoader 之前,都是在检查是否加载过,并不会选择自己去加载。

  • 直到 BootstrapClassLoader,已经没有父加载器了 (无法再继续向上委派了) ,这时候开始考虑自己是否能加载了; 如果自己无法加载,会下沉到子加载器去加载,一直到最底层(向下加载)。如果没有任何加载器能加载,就会抛出 ClassNotFoundException 异常。

那么为什么加载类的时候需要双亲委派机制呢?

采用双亲委派模式的是好处是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次。

其次是,如果有人想替换系统级别的类:String.java 。篡改它的实现,在这种机制下这些系统的类已经被 Bootstrap ClassLoader 加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是 BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值