Java双亲委派机制
2021-08-02 本文已影响0人
OhIAm
一.Java有4种类加载器
- Bootstrap ClassLoader(启动类加载器):加载
<JAVA_HOME>\lib
路径下的类库,通常是rt.jar
- Extension ClassLoader (扩展类加载器) : 加载
<JAVA_HOME>\lib\ext
路径下的类库 - Application ClassLoader(应用程序类加载器):加载用户类路径(classpath)(没有指定的话通常指应用程序的当前目录)上的类库。
- User ClassLoader(用户自定义类加载器):继承ClassLoader,加载指定路径的class文件
二. 双亲委派模型的工作过程:
如果一个类加载器收到类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求,子加载器才会尝试自己去加载。
三.为什么要使用双亲委派模型:
- 为了保证Java程序的安全,如java.lang.Object是所有类的父类,如果用户自己编写了一个同样限定名的类,放在程序的classpath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也会变得一片换乱。
所以是为了避免用户自己编写的类动态替换Java的一些核心类。如Object,String - 避免类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。
四. 双亲委派模型的实现代码
java.lang.ClassLoader的loadClass()方法
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
//首先检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if(c == null) {
try {
//没有加载则调用父加载器的loadClass方法
if(parent != null) {
c = parent.loadClass(name, false);
} else {
//若父加载器为空则默认使用启动器类加载器作为父加载器。
c = findBootstrapClassOrNull(name);
}
}
catch(ClassNotFoundException e){
//如果父类加载器加载失败,说明父类加载器无法完成加载请求
}
if(c == null) {
//在父类加载器无法加载的时候
//再调用本身的findClass()方法来进行类加载
c = findClass(name);
}
}
if(resolve) {
resolveClass(c);
}
return c;
}
上面有3个方法需要注意:
- loadClass: 主要进行类加载的方法,默认的双亲委派机制就实现在这个方法。
- findClass:根据名字加载字节码
- defineClass: 把字节码转化成Class类
五.破坏双亲委派模型
双亲委派过程都在loadClass方法内,想要破坏这种机制,重写其中的loadClass方法,使其不进行双亲委派即可。
- 双亲委派被破坏的例子
- 双亲委派出现之前
JDK1.2之后才引入双亲委派模型,在这之前用户自定义的类加载器肯定是没有遵循双亲委派原则。 - JNDI、JBDC等需要加载SPI接口实现类的情况
JNDI、JDBC通过引入ThreadContextClassLoader(线程上下文加载器,默认就是应用程序类加载器)的方式破坏双亲委派原则。JNDI使用线程上下文加载器去加载所需要的SPI代码,就是父类加载器请求子类加载器去完成类加载的动作,而不是双亲委派模型中先委托父类加载,再由子类加载的原则。