剖析ClassLoader源码,理解双亲委派机制
双亲委派机制是Java的类加载器在处理加载类的任务时的一种分配机制,可以防止类被重复加载或者出现手写类代替系统类的风险,上篇已经全面介绍了双亲委派机制,本篇我们从ClassLoader抽象类的源码上来一探究竟
上篇说到了类加载器会在加载类时先自底向上查找是否被加载过,再由顶向下进行加载,那么他究竟是如何实现的呢,我们可以查看ClassLoader抽象类的源码
ClassLoader 是 Java 类加载机制的核心部分,用于动态加载 Java 类到 JVM 中
首先找到源码
打开IDEA进入任意一个项目,双击SHIFT键即可查找,输入ClassLoader,选择"类"找到java.lang下的ClassLoader抽象类
其中的含有四个核心方法:
loadClass、findClass、defineClass、resolveClass,他们涉及了类加载的全部过程,进入ClassLoader抽象类按CTRL+F查找四个类名逐一查看:
- loadClass():类加载的入口,该方法会根据 “双亲委派模型” 首先请求父类加载器加载类。如果父类加载器无法找到目标类,当前 ClassLoader 才会调用 findClass 方法来尝试加载该类。
- findClass():在 loadClass 的加载逻辑中,如果父类加载器未能找到类,则当前类加载器通过调用 findClass 来查找类的字节码。通常用于自定义类加载器时,负责从自定义源(如文件、网络等)加载类的字节码。
- defineClass():这个方法用于将二进制字节数组转换为 Class 对象。通常在 findClass 中被调用,用于将从外部源获取的字节码定义为 JVM 中的类。
- resolveClass():执行类生命周期中的连接阶段
当有类加载任务时,这四个方法的执行顺序和调用关系大致为:
查看源码
由于我们要探讨的是双亲委派机制的原理,所以我们本篇只解析一下loadClass方法的源码:
他的默认实现是调用重载方法传入参数resolve = false,即不需要调用resolveClass方法,也就是上图的最后一个环节
//默认实现
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
//重载方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
所以我们重点查看重载方法loadClass(String name, boolean resolve),我们逐步分析这个代码的执行逻辑:
1. 获取类加载锁
synchronized (getClassLoadingLock(name))
在加载类时,getClassLoadingLock(name) 会返回与类名关联的锁对象,以确保在多线程环境中同一个类不会被多次加载
2. 检查类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
源码中的注释写道:First, check if the class has already been loaded(检查类是否已经加载),目的是检查类是否已经被当前 ClassLoader 或父类加载器加载。如果类已经被加载,就不会再次加载它
3. 尝试由父类加载器加载类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
如果类未被加载(c == null),首先会尝试使用父加载器加载类。这是 “双亲委派模型” 的体现,即优先让父类加载器处理类加载
这里如果parent != null并不是说明当前类加载器没有父类加载器,而是说明当前类加载器的父类是启动类加载器,由于启动类加载器Bootstarp是由C++编写的,java代码中无法直接引用,他会调用findBootstrapClassOrNull(name) 来查找核心类库。
4. 由当前类加载器尝试加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
如果父类加载器也没有加载成功,则由当前类加载器来进行加载,会调用findClass方法根据类的全限定名,获取类的字节码文件,但是该方法一般由子类进行重写,自定义加载路径以及加载方式
5. 解析并返回类
if (resolve) {
resolveClass(c);
}
return c;