什么是类加载器?
Java中的类加载器(ClassLoader)是JVM中用于动态加载类文件的组件。他将 .class
文件中的字节码加载到内存中,并将其转换为 Class
对象,以供JVM执行。

栈是引用地址,当创建一个对象实例,这个对象名称是存在栈中的,实际上指向的是堆地址

类是一个抽象的,是一个模板
而对象是具体的。是抽象类的实例化

Java中的类加载过程是怎么样的?
类加载指的是 把类加载到JVM中 。把二进制流的形式存储到内存中,之后经过一番解析、处理转换成可用的class类。
二进制流可以来源于class文件,或者通过字节码工具生成的字节码或来自于网络,只要符合格式的二进制流,JVM来着不拒。
类加载流程:
- 加载
在加载阶段,类加载器通过给定的类路径查找类的字节码文件,并将其读取到内存中。
- 类加载器根据类的完全限定名(即包名 + 类名)来查找类文件(例如
com.example.MyClass
)。 - 它会在类路径中查找该类对应的
.class
文件或其他字节码存储位置(如 JAR 包、文件系统、数据库等)。 - 如果类文件被找到,加载器会将字节码读取到内存中,但并不会立即对其进行初始化。
- 连接
连接又可以分为三个阶段:验证、准备、解析。
- 验证:
主要是验证加载进来的二进制流是否符合一定格式,是否规范,是否符合当前 JVM 版本等等之类的验证。
- 准备:
为静态变量(类变量)赋初始值,也即为它们在方法区划分内存空间。这里注意是静态变量,并且是初始值,比如 int 的初始值是 0。
- 解析:
解析阶段将类中的符号引用 转换为直接引用 。比如,方法、字段的符号引用会在这一阶段变成内存中的真实地址。
解析是在类被实际使用时才进行的,不一定在类加载时进行。
- 初始化
初始化阶段是在类第一次被使用时进行的。它的主要任务是执行类的静态代码块(static
初始化块)和静态变量的初始化。
- 静态变量初始化:按照定义顺序为静态变量赋值,静态变量的赋值会执行类中的静态初始化代码。
- 静态代码块:在类初始化时执行,执行类的静态代码块中的内容。
类加载器的作用
- 动态加载类: 在运行时根据需要加载类,而不是在编译时加载所有类。
- 隔离不同的类命名空间: 通过不同的类加载器,可以隔离同名类,使得他们不会相互冲突。

类加载器层次结构
JDK8 的时候一共有三种类加载器:
- 启动类加载器 (Bootstrap ClassLoader):它属于虚拟机自身的一部分,用C++实现的(JDK9后Java实现),主要负责加载<JAVA_HOME>\lib目录中或被Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件,他是所有类加载器的父亲。****
- 扩展类加载器(Extension ClassLoader):他是Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext目录中或被Java.ext.dirs系统变量所指定的路径的类库。
- 应用程序类加载器 ( Application ClassLoader ): ,他是Java实现的,独立于虚拟机。主要负责加载用户类(classpath)上的类库,如果我们没有实现自定义的类加载器,那么这个加载器就是我们程序中的默认加载器。
会逐步委托给父类的加载器(双亲委派机制)
1.自定义加载器
↓
2.应用程序加载器
↓
3.扩展类加载器
↓
4.启动类(根)加载器 (委托到这 优先加载)
在 JDK9 之后 ,类加载器进行了一些修改,主要是因为 JDK9 引入了模块化 ,即 Jigsaw,原来的 rt.jar、tool.jar 等都被拆成了数十个 jmod 文件,已满足可扩展需求,无需保留 <JAVA_HOME>\lib\ext ,所以扩展类加载器 也被重命名为平台类加载器 (PlatformClassLoader),主要加载被 module-info.java
中定义的类。
且双亲委派的路径也做了一定的变化:

在平台和应用类加载器受到加载请求时,会先判断该类是否属于一个系统模块,如果属于则委派给对应的模块类加载器加载,反之才委派给父类加载器。
双亲委派机制
作用:
双亲委派机制 是 Java 类加载器的核心设计原则。这个机制确保了 Java 类加载器具有层次结构,避免了不同类加载器加载相同类的冲突问题。简而言之,每个类加载器在加载类时都会先委托给它的父类加载器加载,如果父类加载器无法加载这个类,它才会尝试自己加载。
确保类加载器按照从父到子的顺序加载类,优先使用核心类库 (如 rt.jar
),防止类的冲突和覆盖。*
防止程序员故意制造一些bug
比如在应用程序加载类 中重写了String类,而且包名一致java.lang.String
如果没有双亲委派机制的话,这个String
就会替换,导致程序运行失败

假设我们在自己的项目中有一个 String
类,同时系统中已经有一个名为 String
的类存在。由于双亲委派机制,我们的自定义类加载器在加载类时会首先去父类加载器中查找,如果父加载器已经加载过该类,它就不会再次加载,从而避免了类的冲突。