Java中类加载采用的是双亲委托机制,为什么要这样处理?又是怎样实现的呢?接下来简单说说我的理解。
Java中类的加载器主要有2种,Java虛拟机自带的加载器和用户自定义的类加载器,自带加载器有3个:
根类加载器( Bootstrap):加载JRE\lib\rt.jar或者-Xbootclasspath指定的jar包,由C++实现,不是ClassLoader 子类。主要加载最基础的启动类的包。
扩展类加载器(Extension):加载JRE\lib\ext*.jar或者-Djava.ext.dirs指定目录下的jar包。主要加载JDK的扩展包。
应用类加载器( System ):加载CLASSPATH或者Djava.class.path知道目录下的类和jar包。加载的是我们用户开发的class,比如项目下的。
剩下的都是用户自定义的类加载器,自定义类加载器需要继承java.lang.ClassLoader,这样用户就可以定制类的加载方式了!
可以看到虚拟机自带的3个加载器分别加载jdk基础包、jdk扩展包、用户创建的class,所以自带的加载器就满足基本需求了,不过有时候我们会需要加载一些特殊class或者不一样的加载方式,就需要自定义了!
我们都知道双亲委派,那么他们是如何实现的呢?
首先我们通过一下代码:
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
可以获取到应用类加载器,跟进源码,大致过程和主要代码如下图:
ClassLoader.getSystemClassLoader()方法中调用了initSystemClassLoader(),在这个方法中通过调用Launcher类的loader属性,通过静态对象launcher找到Launcher类的初始化方法。最终在初始化方法中初始化了Launcher类的内部类ExtClassLoader、AppClassLoader,而这两个初始化最终调用了ClassLoader类的ClassLoader(Void unused, ClassLoader parent)构造函数。
ClassLoader(Void unused, ClassLoader parent)函数设置了加载器的“private final ClassLoader parent;”属性。ExtClassLoader的parent为null;AppClassLoader的parent属性为ExtClassLoader。
在看ClassLoader最重要的方法loadClass(),关键代码如下图:
关键部分已经圈出,主要流程是:
1、查看类是否已经加载
2、如果没有加载,如果parent属性不为null就调用parent的loadClass方法,否则调用Bootstrap类加载器加载。
3、如果还是没有加载成功,就调用当前加载器去加载。
通过第1步实现了通过加载器从下往上查找类是否加载,通过第2、3步实现从上往下加载类,通过这样就实现了类的双亲委托机制!
注意:通过以上源码我们可以看出,类加载器不是通过继承实现委托机制,而是通过组合!
代码中我自己实现了一个最简单的类加载器,他重写了loadClass方法,通过自定义的类加载器创建了一个ClassLoadTest的实例。通过倒数第二行的打印可以看出,创建的实例并不是当前这个类的实例!
在代码中我想把产生的实例转换成ClassLoadTest,程序运行报错。为什么会发生这种情况呢?
因为o这个类是由自定义的类加载器加载的,而ClassLoadTest在启动的时候已经由systemClassLoader加载器加载,即这两个类来源于同一个 Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等!
这样设计有什么好处呢?
通过这样设计Java中需要的基础类无论是被谁加载最终都会往上传递由指定的类加载器加载,保证了内存中基础类只存在一种,比如Object类在程序的各种类加载器环境中都能够保证是同一个类!
类加载关系并不是像普通的层级关系通过继承来实现,而是通过组合实现,每个类加载器都有一个parent属性,指明他的上级加载器,每次加载的类的时候先调用parent去加载,如果parent为null,则直接调用根类加载器,如果都没有加载成功,才调用当前类加载器加载。举例一个应用类加载过程如下图:
每个类加载即使加载的是同一个字节码文件,这两个类也不相同,所以双亲委派就很好地解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载)。