双亲委派机制:
1. 原理:
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
如果程序中没有定义过自己的类加载器,一般情况下,应用程序加载类加载器就是这个程序的默认加载器。
2.双亲委派模型的好处
1. 防止内存中出现多份同样的字节码
比如两个类A和类B都要加载System类:
- 如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
- 如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。
2. 防止内存中出现多份同样的字节码
保护程序安全,防止核心API被随意篡改。
- 例如:如果我们自己自定义了一个
java.lang.String
的类时,如果没有双亲委派机制的话,那么就会将我们程序中的所有运用到String 类 都会使用成我们自定义的类,那么程序就会崩了。有谱 - 有了双亲委派机制后,首先应用程序类加载器接收到
java.lang.String
的请求之后,就会把这个请求委派给它的父类加载器,即 拓展类加载器。 拓展类加载器收到请求后,又会委派给它的父类加载器,即 引导类加载器。引导类加载器检查了这个类的 包名为java.lang
,那么引导类加载器就会对 它进行加载,因为java.lang
是 Java 下的核心类库。这时候就不会交给系统类加载器进行加载了。即便我们写了这个类,但它也是一个无效类,从而提高了安全性。
3. 双亲委派模型注意点:
-
Java虚拟机的第一个类加载器是Bootstrap,这个加载器很特殊,它不是Java类,因此它不需要被别人加载,它嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码),它可以去加载别的类。
这也是我们在测试时为什么发现
System.class.getClassLoader()
结果为null的原因,这并不表示System这个类没有类加载器,而是它的加载器比较特殊,是BootstrapClassLoader
,由于它不是Java类,因此获得它的引用肯定返回null。 -
委托机制具体含义
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?- 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。 - 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
- 还可以直接调用
ClassLoader.loadClass()
方法来指定某个类加载器去加载某个类。
- 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
4. 能不能自己写个类叫java.lang.System
?
-
**答案:**通常不可以,但可以采取另类方法达到这个需求。
**解释:**为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。