JAVA 类加载,鼎鼎有名的 “双亲委派机制
”,
名字很唬,听着很深奥,不知会吓退多少人。
其实呢,就是一个简单的 递归调用,仅此而已。
JAVA类加载的 双亲委派机制
,
是逼格拉满的说法,大白话就是:
如何通过递归方式,找到类的 class 文件,放到虚拟机。
ClassLoader
这个类的实例,即所谓的类加载器,
也就是说,类加载器,就是个对象。
public abstract class ClassLoader {
private final ClassLoader parent;
}
其中有个属性 parent
,这是个重点,后文详细说。
ExtClassLoader
和 AppClassLoader
都继承了 ClassLoader
。
它俩的关系,是在调用 Launcher.getLauncher()
时,构造的类似链表的关系。
除了这两个类加载器之外,还有个 BootstrapClassLoader
,
它是虚拟机内嵌的,C++ 实现的。不归 java 管。
这三个类加载器,加载的类包是不同的。
类加载时,先是 AppClassLoader
调用 loadClass
方法。
为什么是不用 ExtClassLoader
调用,而用 AppClassLoader
调用!
这个问题先放放,等会儿再说。
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
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;
}
}
c = parent.loadClass(name, false);
这句表明是 递归调用
,且只有两层嵌套。
一个没有被加载过的类,加载的过程,画个图大概如下:
如果从代码中,你能想象出这个图,那直接跳到下个标题看。
loadClass
方法,挑出重要的几行
protected Class<?> loadClass(String name, boolean resolve){
// 看下有没有被加载过,有的话直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
// 如果没找到调用 findClass 查找
if (c == null) {
c = findClass(name);
}
}
return c;
}
假设一个类,从没有被加载过,findLoadedClass(name)
返回的就是 null,
AppClassLoader
的 parent
属性,存的是 ExtClassLoader
,
那它调用 loadClass
方法,简化就是
protected Class<?> loadClass(String name, boolean resolve){
// ExtClassLoader 中找对应的类
c = parent.loadClass(name, false);
if (c == null) {
c = findClass(name); // AppClassLoader 加载类
}
return c;
}
ExtClassLoader
的 parent
属性,存的是 null,
那它调用 loadClass
方法,简化就是
protected Class<?> loadClass(String name, boolean resolve){
// BootstrapClassLoader 中找对应的类
c = findBootstrapClassOrNull(name);
if (c == null) {
c = findClass(name); // ExtClassLoader 加载类
}
return c;
}
当代码拆开来看,是不是很简单。
某个类,首次加载,代码执行的结果,一定是:
BootstrapClassLoader
先加载试下,看能不能找到,找到结束,找不到继续ExtClassLoader
加载再试下,看能不能找到,找到结束,找不到继续AppClassLoader
加载再试下,看能不能找到,找到结束,找不到返回 nullparents delegate
应该是原文,被翻译为 “双亲委派”。
“双亲” 姑且理解为递归的两层嵌套吧,
八成是一个不懂程序的人,强行给翻译的。
也不是翻译的不好,就是让人看不懂。
刚刚的分析,举例是一个从未被加载过的类,
省掉了 findLoadedClass
这个方法的说明。
它是查看下,虚拟机是否加载过某个类,如果加载过,直接返回。
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
最终调用的是 native 方法,不讲。
不是我不想讲啊, native 方法我不会呀!
换成俏皮话:臣妾做不到哇
大约理解为:虚拟机里有个类似缓存的东东,
通过类的名字,可以拿到已加载过的类。
总之一句话,不管 AppClassLoader
还是 ExtClassLoader
,
加载类时,先查虚拟机是否加载过该类,
如果加载过,直接返回,否则执行加载过程。
前面遗留了一个问题,类加载时,最先是 AppClassLoader
调用 loadClass
方法,
为什么是这样呢? 看下 Launcher.getLauncher()
是怎么处理的。
public class Launcher {
private static Launcher launcher = new Launcher();
private ClassLoader loader;
public static Launcher getLauncher() {
return 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);
}
// 省略其它代码
}
}
这段代码很清晰,调用 getLauncher
时,返回的 launcher
是初始化时,就确定了的。
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
这行说明 loader
是 AppClassLoader
,
另外这行代码,将 var1
(ExtClassLoader ) 传进去的,
最终是构造了 AppClassLoader
的 parent
属性。
代码不贴出来了,有兴趣自己看吧。
虽然类加载是 AppClassLoader
开始的,
但查找却是从 BootstrapClassLoader
依次往下的。
为什么会这样设计呢?
像 String
类,Object
类都是 BootstrapClassLoader
加载的。
有没有可能,我自己建一个目录,写一个 java.lang.String
,
让虚拟机加载我的这个String 呢?比如下面这样
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("自创String类");
}
}
当然是可以写的,但想想,它能运行成功吗?
不能! 当然不能!!
虚拟机加载的String类,一定是 JDK 自带的。
为啥?因为双亲委派啊。随随便便就被你给替换了,那还了得。
双亲委派保证了类加载的唯一性,
同时也保证了核心类库不被篡改。
这也是个逼格拉满的说法。换个普通的说法就是:
自己写个类,继承 ClassLoader
,重写 loadClass
方法。
具体的细节,本篇不说了。
顺便说一句,自己写的类加载器,继承 ClassLoader
,后,
这个自己写的 类,其 parent
属性,是 AppClassLoader
。
为什么这样,问度娘吧,实在不行,就留言。
绝大多数人,不关心这个,要解释,还得贴一堆代码。
这个也不重要,知道就行了。
所谓双亲委派的类加载机制是:
AppClassLoader 委托 ExtClassLoader 加载,
ExtClassLoader 又委托 BootstrapClassLoader 加载,
人家给加载了,你就不用加载,
人家加载不到,你就自己加载,
结果就是各司其职,各自办好份内的事。
OVER