一.为什么要研究类加载的过程?为什么要研究双亲委派机制?
写在前面的话:为什么要研究类加载的过程?为什么要研究双亲委派机制?
研究类加载的过程就是要知道类加载的时候使用了双亲委派机制。但仅仅知道双亲委派机制不是目的,目的是要了解为什么要使用双亲委派机制,他的原理是什么?知道双亲委派机制的逻辑思想,然后这个思想是否可以被我们借鉴,为我所用。这才是学习知识的目的。
比如:双亲委派机制,避免了类的重复加载,避免了核心类库被修改。那么,我们在做框架设计的时候,框架底层的东西是不是应该是不容被串改的,或者不可以被黑客进攻的,那么我们就可以借鉴双亲委派机制了。
再比如:双亲委派机制的实现使用了责任链设计模式,我们借此可以研究一下责任链设计模式,这样就理解了委派的原理。那么哪些场景我们可以使用责任链设计模式呢?多思考,才是学习的目的和精髓所在。学到的东西,能用在工作中,才是王道。
package com.jason;
import sun.misc.Launcher;
import java.net.URL;
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println("bootstrap Loader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i<urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassLoader加载以下文件");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件");
System.out.println(System.getProperty("java.class.path"));
}
}
输出:
bootstrap Loader加载以下文件:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/classes
extClassLoader加载以下文件
/Users/songyinyu/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:
/Network/Library/Java/Extensions:/System/Library/Java/Extensions:
/usr/lib/java
appClassLoader加载以下文件
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/deploy.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/dnsns.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/jaccess.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/localedata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/nashorn.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/sunec.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/ext/zipfs.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/javaws.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jfxswt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/management-agent.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/plugin.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/ant-javafx.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/bcprov-jdk16-1.46.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/dt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/javafx-mx.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/jconsole.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/packager.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/sa-jdi.
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/lib/tools.:
/Users/songyinyu/IdeaProjects/test/birthday/target/classes:/Users/songyinyu/.m2/repository/org/springframework/spring-webmvc/4.3.12.RELEASE/spring-webmvc-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/org/springframework/spring-aop/4.3.12.RELEASE/spring-aop-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/org/springframework/spring-beans/4.3.12.RELEASE/spring-beans-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/org/springframework/spring-context/4.3.12.RELEASE/spring-context-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/org/springframework/spring-core/4.3.12.RELEASE/spring-core-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/commons-logging/commons-logging/1.2/commons-logging-1.2.jar:
/Users/songyinyu/.m2/repository/org/springframework/spring-expression/4.3.12.RELEASE/spring-expression-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/org/springframework/spring-web/4.3.12.RELEASE/spring-web-4.3.12.RELEASE.jar:
/Users/songyinyu/.m2/repository/com/alibaba/fastjson/1.2.47/fastjson-1.2.47.jar
通过观察,我们发现
引导类加载器,确实只加载了java home下的/jre/lib目录下的类
扩展类加载器加载了java扩展目录里面的类
但是, 应用程序类加载器, 加载的类包含了java home下/jre/lib目录, java home扩展目录下的类, 还有responsitory仓库下的类, 还有idea的类, 还有就是我们的类路径下target的类.
问题来了, 为什么AppClassLoader加载器加载了引导类加载器和扩展类加载器要加载的类呢? 这样加载不是重复了么?
其实, 不会重复加载, appClassLoader主要加载的类就是target目录下的类, 其他目录下的类事实上基本不会加载. 为什么呢? 这是因为双亲委派机制.
上面这个图就是双亲委派机制的图. 这也是类加载的原理。一共分为两部分:
- 一部分是查找
- 另一部分是加载 以我们自定的com.jason.TestJDKClassLoader 为例,我们来看看这个类是如歌被类加载器加载的 第一步:首先我们是由应用程序加载器去查找com.jason.TestJDKClassLoader类,他要去看他已经加载的类中是否有这个类,如果有就直接返回,如果没有,就去加载这个类,但是不是由应用程序类加载器直接加载。而是委托他的父类也就是扩展类加载器去加载
第二步:扩展类加载器也是先搜索,,查看已经加载的类是否有com.jason.TestJDKClassLoader,如果有就返回,如果没有就加载这个类,在加载的时候,也不是由自己来加载,而是委托他的父亲,引导类加载器去加载。
第三步:引导类加载器先查找已经加载的类中是否有这个类,有则返回,没有就去加载这个类。这时候我们都知道 com.jason.TestJDKClassLoader是我们自己定义的,引导类加载器中不可能有,加载失败,所以,他加载这个类,去扫描/lib/jar 包中有没有这个类,发现没有,于是让扩展类加载器去加载,扩展类加载器会去扫描扩展包lib/jar/ext包,里面有没有呢?当然也没有,于是委托应用程序加载器ok, 应用程序类加载器是有的, 于是就可以加载了, 然后返回这个类。 首先, 我们在Launcher的loadClass方法加载类
我们进入 super.loadClass(var1, var2);查看一下
首先调用findLoadedClass(name) 查看是否已经加载过,没有就去调用parent.loadClass(name, false);也就是 extClassLoader加载 还是走这个方法,调用findLoadedClass(name),没有加载过接着往下走,因为extClassLoader的父加载器为空(如下图)
点击super 如下图
也就是说 他会走findBootstrapClassOrNull(name);
最后调用的是一个c++的方法;
到此为止, 我们第一次向上委派查找的过程就完完事了.用图表示就是这样
2.2 类加载器向下委派加载
调用 findClass(name)
三、为什么要有双亲委派机制?
两个原因:
- 沙箱安全机制, 自己写的java.lang.String.class类不会被加载, 这样便可以防止核心API库被随意修改
- 避免类重复加载. 比如之前说的, 在AppClassLoader里面有java/jre/lib包下的类, 他会加载么? 不会, 他会让上面的类加载器加载, 当上面的类加载器加载以后, 就直接返回了, 避免了重复加载.