前言
上一节(2)JVM 类加载之类加载器初始化记录了JVM自带的3中类加载器,也分析了类加载器初始化的流程。那么问题来了:
- 这么几种类加载器之间的关系是什么?
- 有这么几种类加载器,怎么确定一个类由哪个类加载器加载?
- 为什么要分这么几种类加载器呢?一个类加载器不也能加载吗?
- 能够打破双亲委派机制吗?
本文涉及到的源码都在JDK的jre\lib\rt.jar包中。
一、双亲委派机制
1、各个类加载器之间的关系
双亲委派机制用在一个类被加载之前,因为需要判断由哪个类加载器去加载这个类。双亲委派机制在Java运行一个类的流程中的位置如图:
从图中可以看出,引导类加载器是拓展类加载器的父加载器,拓展类加载器是应用程序类加载器的父加载器,应用程序类加载器是自定义类加载器的父加载器。在加载一个类之前,若没有自定义类加载器,则默认是从应用程序类加载器开始加载,逐级委托父级类加载器,最终委托到引导类加载器。若引导类加载器不能加载需要加载的类,则委派拓展类加载器进行加载;若拓展类加载器仍不能进行加载,则委派应用程序类加载器来完成加载。若自定义了类加载器,并使用自定义类加载器对某个类进行加载,则从自定义类加载器开始逐级委托,然后逐级委派。
为什么这几种类加载器是这样的关系呢?因为源码是这样设计的。
在下面分析之前,先放上一个类图:
这是在IDEA中点击ExtClassLoader然后按组合键Ctrl + alt + U看见的类图,其实这个类图没有画完,我在visio中画了一下:
可以发现,拓展类加载器虽然是应用程序类加载器的父级加载器,但应用程序类加载器并不是继承于拓展类加载器,只不过在应用程序类加载器的类中有个parent属性,这个属性是从ClassLoader继承过来的,里面存的值就是拓展类加载器对象的引用,所以看起来他们像是父子的关系。其它的加载器情况与上面的类似,自定义类加载器的parent是应用程序类加载器,拓展类加载器的parent是null,因为其父类加载器引导类加载器是用C++实现的,在Java里获取不到。
我在(2)JVM 类加载之类加载器初始化中记录过,拓展类加载器和应用程序类加载器是在sun.misc.Launcher.getLauncher()这里创建的,那么我们具体再跟进一下,先进入sun.misc.Launcher的构造器:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
...
}
在(2)JVM 类加载之类加载器初始化中分析得到:
- var1是创建的拓展类加载器;
- this.loader是创建的应用程序类加载器。
继续跟进getExtClassLoader():
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
final File[] var0 = getExtDirs();
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
}
看到返回值是:
return new Launcher.ExtClassLoader(var0);
那么继续跟进ExtClassLoader(var0):
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
发现进入到了拓展类加载器的一个有参构造器,里面有一行调用其父类的构造器的代码:
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
发现传进了一个**(ClassLoader)null**,这其实是其父类加载器,继续跟进super:
继续跟进super,parent是上回传进来的null:
再次跟进,parent依然是null:
跟进this,parent是null: