JVM类加载器详解
回顾一下类加载过程
在开始介绍类加载器和双亲委派机制之前,先简单回顾一下类加载过程
- 类加载过程:加载->连接->初始化。
- 连接过程又可分为三步:验证->准备->解析。

加载是类加载过程的第一步,主要完成下面 3 件事情:
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的
Class对象,作为方法区这些数据的访问入口
类加载器
类加载器的概念:
- 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
- 每个 Java 类都有一个引用指向加载它的
ClassLoader。 - 数组类不是通过
ClassLoader创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。
简而言之,类加载器的主要作用就是加载 Java 类的字节码(. class)文件到 JVM 中(在内存中生成一个代表该类的 class 对象)。
类加载器加载规则
JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。
对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。
类加载器总结
JVM 中内置了三个重要的 ClassLoader:
- **
BootstrapClassLoader(启动类加载器)**:最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库(%JAVA_HOME%/lib目录下的rt.jar、resources.jar、charsets.jar等 jar 包和类)以及被-Xbootclasspath参数指定的路径下的所有类。 - **
ExtensionClassLoader(扩展类加载器)**:主要负责加载%JRE_HOME%/lib/ext目录下的 jar 包和类以及被java.ext.dirs系统变量所指定的路径下的所有类。 - **
AppClassLoader(应用程序类加载器)**:面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。

自定义类加载器
我们前面也说说了,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自 java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader 抽象类。
ClassLoader 类有两个关键的方法:
protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,实现了双亲委派机制 。name为类的二进制名称,resolve如果为 true,在加载时调用resolveClass(Class<?> c)方法解析该类。protected Class findClass(String name):根据类的二进制名称来查找类,默认实现是空方法。
如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。
双亲委派模型
JVM 中有三个默认的类加载器
- 启动类加载器 BootStrapClassLoader(最顶端):无法被 Java 程序直接引用,只能加载委派过来的请求。
- 扩展类加载器 ExtClassLoader:可以直接用来加载类,也可以通过委派加载类。Ext 是 Extract 缩写,译为扩展、提取。
- 应用程序类加载器 AppClassLoader(最低端):负责加载类路径所有的类库,开发者同样可以直接在代码中使用这个类加载器。
[!note] 双亲委派模型的工作过程
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
[!note] 具体的流程
JVM 在加载一个类时,会调用应用程序类加载器的 loadClass ()方法来加载这个类,不过在这方法中,会先使用扩展类加载器的 loadClass ()方法来加载类,同样扩展类加载器的 loadClass ()方法中会先使用启动类加载器来加载类;
如果启动类加载器加载到了就直接成功,如果启动类加载器没有加载到,那扩展类加载器就会自己尝试加载该类,如果没有加载到,那么则会由应用程序类加载器来加载这个类。
[!note] 双亲委派机制的好处
- 避免类的重复加载:当父加载器已经加载了该类时,子加载器就没有必要再加载一次。这样可以确保在JVM内存中只有一个版本的类存在,节省了内存开销,也避免了重复定义类。
- 防止核心 API 被篡改:防止核心 API 被随意篡改。例如,java. lang. Object 类位于 rt. jar 中,它是由引导类加载器(Bootstrap ClassLoader)加载的。如果没有双亲委派机制,用户自定义的类加载器可能会恶意替换这些核心 API 的类,比如 String 或者 Object,从而造成安全问题。通过双亲委派模型,JVM 能够保证公共 API 的类加载工作由 JVM 自带的类加载器完成,保持了 Java 运行时环境的纯净性。
- 兼容性:Java 虚拟机出于对现有程序的兼容性考虑,需要这样的层级关系来支持不同版本的类库同时工作,确保应用程序能够运行在更新版本的 Java 环境中而不会出现冲突。

