一直都在听双亲委派 双亲委派 到底是什么东西,在网上看了几篇比较好的博客 自己发个文来记录一下 全当笔记
1.双亲委派发生在类加载阶段。
其作用也就是通过类加载器将class文件加载到虚拟机中以备调用。
在java中自带三个类加载器包括
1.Bootstrap Classloader 用c语言编写是最顶层的加载器。在java代码中如果尝试获取 会返回null。原因很简单,因为是c++语言编写无法用java对象来描述
可通过System.out.println(System.getProperty("sun.boot.class.path"));获取Bootstrap规定的加载路径
会得到:
C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_91\classes
即bootstrap加载器会加载这些路径中的类
2.Extention Classloader 扩展的类加载器
加载路径可以通过下面的语句得到:
System.out.println(System.getProperty("java.ext.dirs"));
得到:
C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext
3.Appclass Classloader 负责加载classpath中的所有类
加载路径可以通过下面的语句得到:
System.out.println(System.getProperty("java.class.path"));
得到:
\workspace\ClassLoaderDemo\bin
至于为什么上面的语句会得到 都在每一个类的源码中 第一步就是读取这几个定义好的加载路径
至于这三个类加载器,是有层级关系的
比如eclipse中在src下的任意一个类运行如下代码(假设类名为TestClassLoader)
如果通过 System.out.println(TestClassLoader.getClassLoader().toString() )
会得到
sun.misc.Launcher$AppClassLoader@4554617c
这证明了 在src目录也就是默认的classpath目录下加载类的加载器默认为 AppClassLoader
如果想得到这个类加载器的父类加载器也就是上面所说的层级关系
可以通过 System.out.println(TestClassLoader.getClassLoader().getParent().toString() )
会得到
sun.misc.Launcher$ExtClassLoader@677327b6
结果说明 AppClassLoader的父加载器是ExtClassLoader.但是如果想得到extClassLoader的父类加载器 得到的会是nullpointerexception 这证明在toString的时候是null。也就是说 extClassLoader的父类加载器是null也就是上面提到的顶级加载器
BootstrapClassloader
上源码:
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
以上代码可以看出 这两个加载器 都继承了同一个父类,这也就是前面提到为什么是层级关系,虽然每一个加载器都有一个父类加载器,但父类加载器 不是这个加载器的父类,这是两个不同的概念 需要区分。
做了如此多铺垫 终于到了双亲委派机制的解读
1.极其重要的一个方法 loadClass
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//findLoadedClass() 是去内存中查找是否加载过这个名字为 name的类,上面的注释是源码中的注释,也是在说 检查这个类是否已经被加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//parent为classload类中的属性,表示它的父加载器,如果父加载器不为空则调用父类的loadClass方法
//可以想到如果当前类加载器没有在内存中找到该类,那么它的父类加载器就会去内存中寻找是否加载了该类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父加载器为空,那么调用BootstrapClassLoader去寻找该类,这也证明了BootstrapClassLoader在java中是
//以null的形式存在的
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//如果还是没有找到,那么调用findClass去查找 也就是从上面提到的路径中找
// 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
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
看完代码,整个过程就清晰很多 PS:这里不要纠结每一个方法都在干什么。我的水平现在还达不到理解这些方法的阶段,因为追踪这些方法 ,最后看到的都是native修饰的本地方法,所以方法的功能我暂时也只能通过注释来判断。
我把上述过程总结一下:
以上面提到的任意一个类TestClassLoader.class为例
首先会调用自身的findLoadedClass()去内存中寻找这个类,在这个例子中就是通过AppClassLoader如果找不到
则会调用其父类加载器 即ExtClassLoader同样去内存中寻找,如果找不到则会再通过父类加载器即BootStrap来寻找,
如果都没有找到则会一层一层返回,即BootStrap通过它自身定义的加载路径加载,如果加载不到则会通过ExtClassLoader
根据自身定义的加载路径加载,如果加载不到则会通过AppClassLoader加载。如果都加载不到,恭喜 ClassNotFund异常等着你。
这就是双亲委派的大致过程,可以这么理解 ,在加载一个类的时候会先自己看一眼 内存里没有 就拜托父类加载器来做,它等着,如果父类加载器还是没找到,就会拜托给父类的父类加载器,直到老祖宗BootStrap加载器也宣告没有找到,但是内存中找不到规找不到,老祖宗还是会很努力的解决问题就会尝试在自己的管辖区域去找,如果找不到 只能告诉儿子,我找也没找到,加载也加载不到,剩下的只能靠你自己了,然后它的儿子就会重复这样的过程,直到最后找到为止。
2.双亲委派为什么要这么做?(一点浅显的理解)
例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类。
如果这时的你很皮(皮有时候是件好事) 你自己写了一个高仿java.lang.Object 放在了src目录中,
你很自豪 ,宣称好了 我把java老祖宗类改了,但事实上是这样的,在你加载这个类的时候,因为名字和rt.jar中的相同,
假设这两个Object都还没有加载,那么首先自身的加载器 AppClassLoader会去内存中找,找不到,直到bootstrap也在内存中找不到,那这时bootstrap会加载rt.jar中的Object,至此加载结束,你自己的高仿是加载不到的。
假设内存中原有的Object已经加载,那么更简单,你兴致冲冲的写完了高仿准备装逼的时候,bootstrap 或者AppClassLoader 已经在内存中找到了(至于哪个加载器寻找哪一个区域的内存空间我不是很清楚 所以也不敢妄下定论)那你的高仿又失去了表现机会,这样的加载模式,保证了java自身类的安全。
以上作为学习心得,水平有限,如有错误,请笑掉大牙并指正,感激不尽。
引用
这篇文章参考并部分摘自
https://blog.csdn.net/briblue/article/details/54973413
通过这个大神学到了很多,也深表感谢,如果侵权,我马上删除。