双亲委派模型
JVM 有 3 个classloader:
- AppClassLoader 也叫 SystemClassLoader
- ExtClassLoader,在 java9 被废弃,使用 PlatformClassLoader。用于加载在JDK安装目录下的
jre/lib/ext
目录。 - BootstrapClassLoader。这个是 native 的 class loader。通过 java 无法获取到该 class loader。即通过PlatformClassLoader的getParent()会是 null。用于加载
jar/lib
目录。
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("AppClassLoader: " + appClassLoader);
// 获取平台类加载器
// ClassLoader platformClassLoader = ClassLoader.getPlatformClassLoader();
// System.out.println("PlatformClassLoader: " + platformClassLoader);
// 获取扩展类加载器。java9之前和之后的返回结果会有不同
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("ExtClassLoader: " + extClassLoader);
// 获取启动类加载器(BootstrapClassLoader)
// 注意:BootstrapClassLoader 是用本地代码实现的,Java 中获取不到它的实例,
// 因此当请求它的父类加载器时会返回 null
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("BootstrapClassLoader: " + bootstrapClassLoader);
一个常见的面试题,如果在应用程序中定义了一个java.lang.String类,是不会加载这个自定义的 String 类的。因为,JVM 使用了双亲委派机制。
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;
}
}
sofa ark
sofa ark 中,在 JVM 之上,构造了 ArkContainer。在 ArkContainer 中,构造了 BizClassLoader,通过 BizClassLoader 重新加载了业务 main 类。因此,业务类一直都在 BizClassLoader 中。
在 java 中,ClassA 如果依赖了 ClassB,如果 ClassA 的 classLoader 是classLoaderBiz,那么在加载 ClassB 时,也会先用是classLoaderBiz的 loadClass方法进行加载。注意这里说的是classLoaderBiz的 loadClass方法,而不是classLoaderBiz这个 class loader。因为如果在classLoaderBiz使用了双亲委派,那么可能被其父加载器加载。
在 sofa ark 中,分为 BizClassLoader 和 PluginClassLoader。
PlunginClassLoader 需要将对外暴露的类、 package、resource 通过 export 的方式暴露出去。
在使用 BizClassLoader 进行加载的时候会判断是否为 export 决定是否使用该export的 class loader。
@Override
protected Class<?> loadClassInternal(String name, boolean resolve) throws ArkLoaderException {
Class<?> clazz = null;
// 0. sun reflect related class throw exception directly
if (classloaderService.isSunReflectClass(name)) {
throw new ArkLoaderException(
String
.format(
"[ArkBiz Loader] %s : can not load class: %s, this class can only be loaded by sun.reflect.DelegatingClassLoader",
bizIdentity, name));
}
// 1. findLoadedClass
if (clazz == null) {
clazz = findLoadedClass(name);
}
// 2. JDK related class
if (clazz == null) {
clazz = resolveJDKClass(name);
}
// 3. Ark Spi class
if (clazz == null) {
clazz = resolveArkClass(name);
}
// 4. pre find class
if (clazz == null) {
clazz = preLoadClass(name);
}
// 5. Plugin Export class
// 本文重点关注这里
if (clazz == null) {
clazz = resolveExportClass(name);
}
// 6. Biz classpath class
if (clazz == null) {
clazz = resolveLocalClass(name);
}
// 7. Java Agent ClassLoader for agent problem
if (clazz == null) {
clazz = resolveJavaAgentClass(name);
}
// 8. post find class
if (clazz == null) {
clazz = postLoadClass(name);
}
if (clazz != null) {
if (resolve) {
super.resolveClass(clazz);
}
return clazz;
}
throw new ArkLoaderException(String.format("[ArkBiz Loader] %s : can not load class: %s",
bizIdentity, name));
}