浅析Java双亲委派机制及其作用
双亲委派机制:当某个类加载器准备加载一个.class字节码文件时,它首先将这个加载任务委派给上一级类加载器,上一级加载器再委派到更上一级类加载器,递归这个操作直到最顶级的类加载器。
一、类加载器的类别
在介绍Java的双亲委派机制的时候,不得不提ClassLoader(类加载器)
我们编译的Java代码是如何在JVM中运行的?首先源程序(.java文件)被Java编译器编译为.class字节码文件,然后ClassLoader负责将这些class文件加载到JVM中去执行。
如上图所示:JVM提供了三层类加载器
-
BootStrapClassLoader(引导类加载器)
引导类加载器:用
C++
编写,是Java自带的类加载器,用于加载JDK内部的类;主要负责加载核心的类库,构造ExtensionClassLoader
和AppClassLoader
。Bootstrap类加载器用于加载JDK中$JAVA_HOME/jre/lib下面的那些类,比如rt.jar包里面的类。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到引导类加载器的引用,所以不允许直接通过引用进行操作。
-
ExtensionClassLoader(扩展类加载器)
扩展类加载器:主要负责加载 jre/lib/ext 目录下的一些扩展的jar包或-Djava.ext.dirs指定目录下的jar包。主要用于加载JDK扩展包里的类。一般$JAVA_HOME/lib/ext下面的包都是通过这个类加载器加载的
Java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。 -
AppClassLoader(系统类加载器)
系统类加载器:负责java -classpath或-Djava.class.path所指定目录下的类与jar包
主要负责加载应用程序的主函数类。用来加载开发人员自己平时写的应用代码的类的,加载存放在classpath路径下的那些应用程序级别的类的。
Java
编写,加载程序所在的目录 -
CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
二、工作原理
- 如果一个类加载器收到类加载的请求,并不会自己先加载,而是将加载请求委托给父类的加载器进行加载;
- 如果父类的加载器还存在其父类加载器,则进一步向上委托,递归这个操作,直至到达最高级的引导类加载器;
- 如果父类加载器可以完成加载请求,则成功返回,如果父类加载器不能完成加载请求,则子加载器才会尝试自己去加载,这就是双亲委派机制;
- 父类加载器一层层向下分派任务,如果子类加载器能加载则加载此类,如果直到系统类加载器也无法加载,则抛出异常。
从上图我们就容易理解,当一个Test.class文件要加载时:
-
不考虑用户自定义的类加载器,首先会在AppClassLoader中检查是否加载过,如果已经加载过就无需在加载了;如果没有,就会委派到父加载器,然后调用父加载器的loadClass方法
-
父类加载器同理也会先检查是否加载过,如果没有再向上委派
这个类似递归的过程,直到到达BootstrapClassLoader之前,都只是检查是否加载过,并不会选择自己加载
-
直到BootstrapClassLoader,已经没有父加载器了,这是开始考虑自己是否能加载了,如果自己不能加载,则下沉到子类加载器去加载,直到最底层
-
如果没有任何类加载器能够加载,则抛出ClassNotFoundException异常
三、双亲委派机制的作用
- 防止同一个
.class
文件被重复加载。通过委派机制向上问一问,如果已经加载过,就不用再加载一次,保证数据安全 - 保证核心
.class
文件不能被篡改。通过委派机制,即使不小心篡改了核心.class
文件,也不会被执行,保证了系统级别的类的安全性
四、代码浅析
举例1:建立一个java.lang.String类,写上static代码块
在另外的程序中加载 String 类,看看加载的 String 类是 JDK 自带的 String 类,还是我们自己编写的 String 类
从输出结果可以看到,程序并没有输出静态代码块的内容,自定义String类并没有被加载,所以加载的是JDK内置的String类
这是因为我们自定义的String类本应被AppClassLoader加载,但AppClassLoader并不会先自己加载,而是把加载请求委托给父类的加载器去执行,到了ExtensionClassLoader发现String类不归自己管,于是委托给父类的加载器(BootStrapClassLoader),引导类加载器发现是java.lang包,所以加载的就是JDK内置的String类
举例2:在自定义的String类中写个main()方法
由于双亲委派机制加载的是JDK内置的String类,而在引导类加载器中的核心类库API里的String类并没有main()方法
举例3:在java.lang包下自定义一个AbcTest类
报错,java.lang包下不允许自定义类