前言
在深入openjdk源码全面理解Java类加载器(上 – JVM源码篇)我们分析了JVM是如何启动,并且初始化BootStrapClassLoader的,也提到了sun.misc.Launcher被加载后会创建ExtClassLoader和AppClassLoader。关于类加载的基础知识请参考虚拟机类加载机制(上)。这篇文章主要从Java源码层面总结一下双亲委派、TCCL的应用等,然后再聊聊自定义类加载器的注意事项。
一、双亲委派
1.1 类加载器结构
直接在idea里看看AppClassLoader的继承关系(ExtClassLoader一样):
AppClassLoader和ExtClassLoader都继承自URLClassLoader,URLClassLoader继承自SecureClassLoader,最终继承自ClassLoader。类加载的核心方法以private native定义在ClasssLoader中,只能由ClassLoader调用,所以所有的自定义类加载器都必须直接或间接继承ClassLoader。
1.2 双亲委派
加载类的核心方法是loadClass,默认实现在ClassLoader中:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//同步锁,可能是一个和name对应的Object,也可能是this
//取决于类加载器是否具备并行能力
//首先检查类是否被本类加载器加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
//如果没有找到需要加载的类
long t0 = System.nanoTime();
try {
//使用父类加载器加载类
//如果parent不为null,说明设置了父加载器,直接用parent
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果parent为null,使用BootStrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//如果父类加载器没能加载到类,使用本类加载器加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//是否需要立即解析
resolveClass(c);
}
return c;
}
}
注:关于getClassLoadingLock,可参考:关于类加载的并发控制锁。
从loadClass的逻辑中可以很清晰的看到双亲委派的实现:首先查看类是否已经加载,如果未加载则委派给父类加载,如果父类加载器没能加载成功,那么才由本类加载器加载。
通常情况下,所有Java实现的类加载器都是调用ClassLoader的这个loadClass方法,所以本类加载和父类加载器都是这个逻辑:本类加载器委托父类加载器,父类加载器委托租父类加载器等等。顶层类加载器如果无法加载则依次回溯。
二、自定义类加载器
自定义类加载器需要直接或间接继承ClassLoader,最简单的一个自定义类加载器就是继承ClassLoader,重写其findClass方法,通过ClassLoader.defineClass方法创建一个Class类(defineClass最终会调用ClassLoader的native方法):
public class MyClassLoader extends ClassLoader {
private URLClassPath ucp;
public MyClassLoader(String path, ClassLoader parent) throws Exception {
super(parent);
this.ucp = new URLClassPath(new URL[]{
new File(path).toURI().toURL()});
}
static {
ClassLoader.registerAsParallelCapable();
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String usePath = name.replace('.', File.separatorChar).concat(".class");
Resource resource = ucp.getResource(usePath, false);
if (resource != null) {
try {
byte[] bytes = resource.getBytes();
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException var) {
return null;
}
} else {
return null;