从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
Java自带了以下三个ClassLoader:
- 启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将存放在<JAVA_HOME>\lib
目录中的,或者被-Xbootclasspath
参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。 - 扩展类加载器(Extension ClassLoader):
这个加载器由sun.misc.Launcher$ExtClassLoader
实现,它负责加载<JAVA_HOME>\lib\ext
目录中的,或者被java.ext.dirs
系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。 - 应用程序类加载器(Application ClassLoader):
这个类加载器由sun.misc.Launcher$App-ClassLoader
实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()
方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)
上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
如有必要,我们还可以实现自己的类加载器。这些类加载器的关系一般如下图所示
User ClassLoader Application ClassLoader User ClassLoader Extension ClassLoader Bootstrap ClassLoader
接下来就让我们验证下吧。我随便写了个加载器,其结构是否如上图所示呢?
public class MyClassLoader extends ClassLoader{
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
System.out.println(classLoader.getParent().getParent().getParent());
}
}
其输出如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@133314b
null
可以看到前面两行输出确实是AppClassLoader和ExtClassLoader,可后面怎么成了null呢?接下来就从ClassLoader的源码中一探究竟吧(限于篇幅,只展示部分比较核心的代码)。
首先来看下loadClass()
,这里是双亲委派模型的核心。
//定义父加载器
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查是否已经被自身加载 已经加载过不会重新加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//父加载器不为空 检查是否被父加载器加载过
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//没有父加载器 检查是否被Bootstrap ClassLoader加载过
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 父加载器与Bootstrap ClassLoader都没有加载过 开始执行加载逻辑
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;
}
}
这样做有什么好处呢?
- 首先避免了重复加载可能导致的性能问题。
- 保证了Java基础类库不会被覆盖,具有一定的安全性。
那上下级关系又是如何确定的呢?
接着看源码,首先看下ClassLoader的几个构造方法。
//无参构造 默认使用SystemClassLoader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
//传入parent的构造
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
//实际逻辑
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
在上面的验证中我们调用了无参构造方法,可以看到其调用了getSystemClassLoader()
用于返回parent,其最终是调用了sun.misc.Launcher.getLauncher().getClassLoader()
,此方法源码如下:
public ClassLoader getClassLoader() {
return this.loader;
}
这个loader又是哪里来的呢?好在Launcher只有一个构造方法。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//初始化 ExtClassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//使用ExtClassLoader作为父加载器创建AppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
从代码中可以清楚的了解到其首先创建了ExtClassLoader
,随后使用ExtClassLoader
作为父加载器创建了AppClassLoader
,进而保证了其上下级顺序。