先说说什么是类加载器
- 类加载器是Java虚拟机提供给应用程序去实现和获取类和接口字节码的技术,用于动态加载Java类到内存中(将字节码转换为byte[ ])的组件
四种类加载器
- 启动类加载器(Bootstrap ClassLoader),加载核心类,比如String类
- 扩展类加载器(Extension ClassLoader),加载扩展类
- 应用程序类加载器(Application ClassLoader),加载classpath中的类
- 自定义类加载器,一般重写findClass方法
注: JDK9之后扩展类加载器 (Extension ClassLoader) 变为平台类加载器 (Platform ClassLoader)
什么是双亲委派机制
-
每个Java实现的类加载器中都有一个属性,可以理解为存放的是其父类加载器
启动类加载器是用本地代码,通常是C/C++写的,所以用java代码不能获取,结果返回为null,所以扩展类的parent属性是null,但是逻辑上来说启动类加载器还是相当于它的父类
JDK9引入了module的概念,类加载器在设计上发生很大改变,启动类加载器使用java编写,不过仍然无法通过java代码获取到,返回依然是null
流程概述
-
自底向上进行查找: 如果类没有被当前加载器加载过,则查找父类有没有加载过,如果加载过则返回,如果没有则继续向上查找
-
由顶向下进行加载: 如果直到启动类加载器都没有被加载过,则由父类尝试加载,再由子类尝试,直到被加载并返回
双亲委派机制的好处
- 防止类的重复加载:通过双亲委派机制,每个类加载器在加载类时都会委派给其父加载器,因此可以避免同一个类被多个加载器加载,保证了类的唯一性
- 保护类的安全性:双亲委派机制可以防止恶意类的加载,即使是恶意类也无法绕过双亲委派机制直接被加载,它们必须被放置在被信任的类路径下才能被加载
- 保护核心类库的完整性:通过双亲委派机制,Java核心类库(如java.lang包)被放置在引导类加载器的类路径下,这样可以保护核心类库的完整性,防止被篡改或替换,确保了Java运行环境的稳定性和安全性
ClassLoader源码简单分析
-
ClassLoader中有几个重要方法
1.类加载的入口,提供双亲委派机制,内部调用findClass
loadClass(String):Class<?>2.类加载器子类实现,获取二进制数据调用defineClass
findClass(String):Class<?>3.做类名校验,调用虚拟机底层方法将字节码加载到内存
defineClass(String, byte[], int, int):Class<?>4.执行类生命周期连接阶段
resolveClass(Class<?>):void -
ClassLader的loadClass方法
//1.String name 被加载类名
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//2,加锁防止在多线程条件下重复加载类
synchronized (getClassLoadingLock(name))
{
//3,查找此类是否被加载过,若被加载则返回Class对象
Class<?> c = findLoadedClass(name);
//4,如果c为空,则说明未被加载过,否则,是被加载过,跳到10
if (c == null) {
long t0 = System.nanoTime();
try {
//5,判断父类加载器是否为null
if (parent != null) {
//6,如果不为空,则调用父类loadClass方法
c = parent.loadClass(name, false);
}
else {
//7,如果为空,说明父类为启动类加载器,调用本地方法来加载
c = findBootstrapClassOrNull(name);
}
}
catch (ClassNotFoundException e) {
}
//8,如果c为空,说明其父类都没有加载成功,则由当前类加载器来进行加载
if (c == null) {
//9,加载类
c = findClass(name);
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
//10,返回Class对象
return c;
}
}
打破双亲委派机制的方式
- 自定义类加载器,重写loadClass方法,不再实现双亲委派机制
- JNDI,JDBC,JCE,JAXB 和 JBI 等框架使用了SPI机制+线程上下文类加载器
- OSGI 允许同级类加载器相互调用