三层类加载器、双亲委派模型--Java类加载器总结分析
在这篇博文中,我们提到,JVM类加载的第一步就是”加载“,而这一步就是由Java的类加载器完成
类加载器的作用:通过一个类的全限定名来获取描述该类的二进制字节流
注意:对于任意一个类,都必须由它的类加载器和这个类本身一起确立其在JVM中的唯一性,即 :即使两个类来源同一Class文件,被同一个JVM加载,但只要加载他们的类加载器不同,这两个类就不相等!
package Q2;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.indexOf(".") + 1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("Q2.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof Q2.ClassLoaderTest);
}
}
输出结果为
class Q2.ClassLoaderTest
false
以上程序构造了一个简易的类加载器,用这个类加载器去加载测试类,根据结果显示,obj对象的确是Q2.ClassLoaderTest的实例,但是类型检查却返回false,
这是因为虚拟机中同时存在了两个ClassLoaderTest类,一个由虚拟机的应用程序类加载器加载,一个由自定义类加载器加载,虽然来自同一Class文件,但仍属于两相互独立的类,在对象所属对象类型检查时即返回false。
三层类加载器
Java中存在以下三类类加载器
1. 启动类加载器(Bootstrap ClassLoader):这个类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数指定的路径中存放的,且是JVM能够识别的类库。只有这个类加载器是使用C++语言实现,是JVM的一部分,其他类加载器全部由java语言实现,独立存在于JVM外部,且都继承于java.lang.Classloader抽象类。
2. 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\home\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库,开发者可以直接使用此类加载器加载Class文件。
3. 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上的所有类库,开发者同样可以直接使用此类加载器,如果没有指定,则默认使用此类加载器。
三层类加载器、双亲委派模型
JDK9之前,Java应用都由这三种类加载器相互配合完成,如果有必要,用户可以自定义类加载器进行拓展。
如图所示, 除了最顶层的启动类加载器外,其余的加载器都必须有父类加载器,只不过这里一般使用组合关系来复用父类的代码。
双亲委派模型工作过程:
如果一个类加载器收到了类加载请求,它自身不会第一时间去尝试加载这个类,而是把这个请求委派给自己的父类加载器去执行,因此所有的加载请求最终都会传输到最顶层的启动类加载器,只有父类加载器反馈自己无法完成加载时(搜索范围内没有找到需要的类),子类加载器才会去尝试自己完成加载。
双亲委派模型的好处:
可以保证无论哪一个类加载器要要加载类C,都可以保证类的唯一性。如无论那个类加载器要加载Object类,最终都会轮到启动类加载器去加载Object类。反之,如果没有双亲委派的话,由各个类自行加载,那么就可能出现多个不同的类C,导致程序混乱。
双亲委派模型的源码分析:
protected synchronized Class<?> loadClass(String name,boolean resolve)
throws ClassNotFoundException {
//首先检查请求的类是否已经加载过
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name,false); //父类加载器不为空,调用父类加载器
}else {
c = findBootstrapClassOrNull(name); //父类加载器为空,则使用启动类加载器
}
}catch (ClassNotFoundException e) {
//如果父类加载器抛出ClassNotFoundException
//说明父类无法完成加载请求
}
if (c == null) {
//在父类无法加载时,调用本身类加载器进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
注意:
在JDK9以后,Java类库实行模块化,不同的类库组成不同的模块,类加载系统也随之发生了一些变化,比如:扩展类加载器被平台类加载器替代等
如上图所示,JDK9及以后虽然维持着三层类加载器和双亲委派的架构,但类加载的委派关系发生了变动
当平台及应用程序类加载器收到类加载请求,在委派给父类加载器之前,会判断该类能否归属到某一个系统模块中,如果可以找到归属关系,就优先委派给负责加载那个模块的加载器进行加载!