一、双亲委派机制
1.1概念
JVM类加载器是有亲子层级结构的,如下图:
这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托启动类加载器,顶层启动类加载器在自己的类加载路径里找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载。
1.2为什么要有双亲委派机制
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改。
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。
我们思考下为什么要从下线上委托,从上往下加载。设想一下,如果我们是从下往上加载的话,会是多么可怕的一件事情:我们不小心定义了一个java.lang.String类和jdk的String一样的,如果是从下往上加载的话,那么我们自己定义的会优先被加载,那么jdk的String就无法被加载了,那么String中的方法也就无法被调用了,这将是多么可怕的一个事情。
1.3JVM中存在的3个默认的类加载器
-
BootstrapClassLoader:根类加载器:它负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class。
-
ExtClassLoader:展类加载器:它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。
-
AppClassLoader:应用类加载器:它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径。
3个类加载器之间的关系:AppClassLoader的父加载器是ExtClassLoader;,ExtClassLoader的父加载器是BootstrapClassLoader。需要注意的是这里说的父加载器并不是说他们之前存在继承关系,而是在类加载器中有一个parent属性。
1.4类加载过程
JVM在加载一个类时,会调用AppClassLoader的loadClass方法来加载这个类,不过在这个方法中,会先使用ExtClassLoader的loadClass方法来加载类,同样ExtClassLoader的loadClass方法中会先使用BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会自己尝试加载该类,如果没有加载到,那么则会由AppClassLoader来加载这个类。
所以,双亲委派——指得是,JVM在加载类时,会委派给ExtClassLoader和BootstrapClassLoader进行加载,如果没加载到才由自己进行加载。
1.5类加载器源码分析
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
可以看到在loadClass中现在本缓存里面找,如果找不到就委托上级去加载。
1.6那些情况需要打破双亲委派机制
tomcat打破双亲委派:
- tomcat是web容器,一个web容器可以部署多个程序,不同程序依赖的第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一个份,因此要保证好相互的隔离。并且为了安全tomcat容器依赖的类库和应用所以来的类库也需要隔离。这就打破了双亲委派。
- 众所周知jsp文件也是最终编译成class文件被夹在到虚拟机的。对于class修改了,如果类名一样类加载器会直接去取内存中已经存在的class,修改就不会生效,tomcat在修改jsp页面不需要重启服务也可以生效。说明也打破了双亲委派机制。tomcat是如何做的呢?他给每个JSP都准备了一个Jsp类加载器。当jsp改变就卸载类加载器,重新创建类加载器,重新加载jsp文件。