全栈之路-双亲委派机制

低谷期是我自己熬过来的,我的温柔是一次次教训给的,快乐早就没了,我没有对不起任何人,唯独对不起我自己,所以现在少了谁的陪伴都无关紧要

Posted by yishuifengxiao on 2020-09-03

一 基本定义

类加载器是jre的一部分,负责动态将类添加到Java虚拟机。当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

双亲委托机制采用的是”向上委托,向下查找“,其步骤如下:

第一步(向上委托) 当前类加载对.class进行加载,先会找到上级类加载器AppClassLoader,然后去缓存查是否有已加载的类,如果没有则去上级ExtClassLoader缓存查找是否有已加载的类,如果没有则再往上Bootstrap缓存找是否有已加载的类,如果没有就会进入第二步,反之上面任何一步缓存查找有的话,都会直接返回缓存里加载了的.class,而不会继续往上级找。

第二步(向下查找) 第一步缓存找不到时就会进入第二步。此时已经到了Bootstrap,Bootstrap会先到其对应的加载目录(sun.mic.boot.class路径)去看看当前有没这个类加载,如果有就加载返回返回;没有则往下级ExtClassLoader的对应加载目录(java.ext.dirs路径)找,有就加载返回,无就往下走;走到AppClassLoader然后去其对应加载目录(java.class.path路径)加载,有就加载,没有则让子类找,如果还失败就抛异常,然后调用当前ClassLoader.findClass()方法加载。

二 类加载分类

1、启动类加载器 bootstrap classloader :加载jre/lib/rt.jar

c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

最顶层类加载器,加载java核心类库即%JRE_HOME%\lib下的rt.jar、charsets.jar和class等, 可通过java -Xbootclasspath/a:path(追加)、-Xbootclasspath/p:path(优先加载)、-Xbootclasspath:bootclasspath(替换jdk的rt.jar的加载)指定。

2、扩展类加载器 extension classloader :加载jre/lib/ext/*.jar

java编写,加载扩展库,如classpath中的jrejavax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器

扩展类加载器,加载%JRE_HOME%\lib\ext的jar和class文件,可用-Djava.ext.dirs=./plugin:$JAVA_HOME/jre/lib/ext(“:”是作为分隔符,代表./plugin和ext目录的都被扩展类加载器加载)指定。

3、应用程序类加载器 application classloader:加载classpath上指定的类库

java编写,加载程序所在的目录,如user.dir所在的位置的class

4 CustomClassLoader(用户自定义类加载器)

java编写,用户自定义的类加载器,可加载指定路径的class文件

用户自定义的类加载器,默认使用双亲委派,委托上级来加载。

三 工作流程

  1.   当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。

  2.   当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。

  3.   如果Bootstrap ClassLoader加载失败(在\lib中未找到所需类),就会让Extension ClassLoader尝试加载。

  4.   如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。

  5.   如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。

  6.   如果均加载失败,就会抛出ClassNotFoundException异常。

例子:

  当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理会先检查自己是否已经加载过,如果没有再往上。注意这个过程,直到到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的。如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

四 双亲委派机制的作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

双亲委派优点

1 安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String

2 避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)

大家所熟知的Object类,直接告诉大家,Object默认情况下是启动类加载器进行加载的。假设我也自定义一个Object,并且制定加载器为自定义加载器。现在你会发现自定义的Object可以正常编译,但是永远无法被加载运行。

这是因为申请自定义Object加载时,总是启动类加载器,而不是自定义加载器,也不会是其他的加载器。

双亲委派缺点

人和事物都有缺陷,双亲委派机制也不例外,三次得到破坏:

1-jdk1.2之间,用户直接去调用loadClass()方法;不能保证双亲委派机制的基本规则。后改成findClass()方法。

2-双亲委派机制的自我缺陷,使用了线程上下文类加载器。这种行为打破了双亲委派机制模型的层次关系来逆向使用类加载器,实际上违背了双亲委派机制的一般性原则。

3-用户对程序动态性的追求而导致的。例如鼠标,键盘灯热部署。