类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
类加载器只参与加载过程中的字节码获取并加载到内存这一部分。
类加载器的作用是什么?
类加载器(ClassLoader)负责在类加载过程中的字节码获取并加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将将byte[]转换成方法区和堆中的数据
1.类加载器的分类
1.1启动类加载器(重要)
出于安全性考虑,无法让程序员在代码中去获取到启动类加载器。
在代码中获取到的为null,则为
启动类加载器。
通过启动类加载器去加载用户
jar
包:
1.放入jre/lib下进行扩展(不推荐)
2.
使用参数进行扩展(推荐,使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展
)
1.2扩展类加载器(通用但不重要)
扩展类加载器和应用程序类加载器,
它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录 或者指定jar包将字节码文件加载到内存中
默认加载Java安装目录/jre/lib/ext下的类文件。
通过扩展类加载器去加载用户
jar
包:
1.放入jre/lib下进行扩展(不推荐)
2.
使用参数进行扩展(推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录
)
1.3应用程序类加载器
加载classpath下的类文件 :
我们项目中自己编写的类和
接口的文件,以及第三方jar
包(如maven依赖)中的类和接口的文件。
2.双亲委派机制
2.1父类加载器
⚫
每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器,可以理解为它的上级, 并不是继承关系。
⚫
应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空。
⚫
启动类加载器使用C++编写,没有上级类加载器。
⚫
类加载器的继承关系可以通过classloader –t 查看
2.2双亲委派机制
双亲委派机制指的是:
自底向上查找是否加载过,再由顶向下进行加载(
向下委派加载起到了一个加载优先级的作用,从上至下优先级依次降低。
)
⚫
在类加载的过程中,每个类加载器都会先检查是否已经加载了该类,如果已经加载则直接返回,否则会 将加载请求委派给父类加载器
⚫
如果类加载的
parent
为
null
,则会提交给启动类加载器处理。
⚫
如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。
⚫
第二次再去加载相同的类,仍然会向上进行委派,如果某个类加载器加载过就会直接返回
2.3三个问题
01.重复的类
如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
答:启动类加载器加载,根据双亲委派机制,它的优先级是最高的
02.String类能覆盖吗
答:不能,会交由启动类加载器加载在rt.jar包中的
String类。
启动类加载器在检
查的时候就会返回
string类
03.类加载器的关系
应用类加载器的父类加载器是扩展 类加载器,扩展类加载器没有父类加载器,但是会委派给启动类加载器加载
2.4双亲委派机制有什么用
1.保证类加载的安全性
通过双亲委派机制,让顶层的类加载器去加载核心类,避免恶意代码 替换JDK中的核心类库,比如 java.lang.String,确保核心类库的完整性和安全性。
2.避免重复加载
双亲委派机制可以避免同一个类被多次加载,上层的类加载器如果加载过类,就会直接返回该类,避免重复加载。
![](https://img-blog.csdnimg.cn/direct/6cd0a6f2c6b245aba0b7b61e5d80b552.png)
3.打破双亲委派机制
![](https://img-blog.csdnimg.cn/direct/673add33747946c5852a4d02732982ee.png)
3.1自定义类加载器
Tomcat使用了自定义类加载器来实现应用之间类的隔离。每一个应用会有一个独立的类加载器加载对应的类
自定义类加载器默认的父类加载器是应用程序类加载器Application
两个自定义类加载器加载相同限定名的类,不会冲突吗?
⚫
不会冲突
,在同一个Java虚拟机中,只有
相同类加载器+相同的类限定名
才
会被认为是同一个类。
⚫
在Arthas中使用sc –d 类名的方式查看具体的情况。
正确的去实现一个自定义类加载器的方式是重写
findClass
方法,这样不会破坏双亲委派机制
3.2线程上下文类加载器
⚫
JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。
⚫
DriverManager类位于rt.jar包中,由启动类加载器加载
⚫
依赖中的mysql驱动对应的类,由应用程序类加载器来加载。
⚫
DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制
![](https://img-blog.csdnimg.cn/direct/0364e9a5b2f24228be7c911ec463cbad.png)
⚫
DriverManage使用SPI机制,最终加载jar包中对应的驱动类
![](https://img-blog.csdnimg.cn/direct/98683599e0ca4c70a854c3a145d54cf4.png)
⚫
SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。
![](https://img-blog.csdnimg.cn/direct/4fcbc5893b1f49f6a32f60d8d48c6314.png)
注意:
没有打破双亲委派机制
JDBC
只是在
DriverManager
加载完之后,通过初始化阶段触发了驱动类的加载,类的加载顺序依然遵循双亲委派机制
4.JDK9之后的类加载器
JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform
ClassLoader)