类加载包含两部分:一部分是被谁加载,另一部分是如何加载。
被谁加载是指 ClassLoader 的双亲委派模型,如何加载是指类的加载过程。
这篇博客介绍双亲委派模型,特别感谢 @书呆子Rico老师 的博客
ClassLoader 结构核心思想
class ClassLoader {
/**
* 父加载器
*/
ClassLoader parent;
/**
* 已经加载的 class
* 这个属性是我假想的,为了配合 {@link #findLoadedClass} 的猜想
*/
Map<String, Class> loadedClasses;
/**
* 尝试从已经加载的类中寻找,如果没有返回 null
* 真实的实现是 native 方法,我猜想是存了一个类名到类实例的映射,如下
*/
Class<?> findLoadedClass(String name){
return loadedClasses.get(name);
}
/**
* 当前类加载器尝试去加载一个类,内部实现就是类的加载过程
* 为什么说尝试?因为当前类加载器可能无法加载这个类,会返回 null
*/
Class<?> findClass(String name){}
/**
* 双亲委派模型的实现,也是类加载的直接入口
*/
Class<?> loadClass(String name){}
}
双亲委派模型
当类加载器尝试加载一个类时,会首先查看自己加载过的类。如果没有查找到,不是马上去加载,而是去询问父加载器(这是一个递归的过程)。如果父加载器也没有加载,自己才会加载。双亲委派模型是通过组合的方式实现的,不是继承。
双亲委派实现自己实现的伪代码
Class<?> loadClass(String name) {
// 查找自己加载过的类
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
// 尝试让父类加载
if (parent == null) {
// 调用 BootstrapClassLoader 去加载,后面介绍 Java 类加载器的层级结构
} else {
loadedClass = parent.loadClass(name);
}
// 如果父类没有加载,自己加载
if (loadedClass == null) {
loadedClass = findClass(name);
}
}
// 返回最终结果,此时还有可能为 null
// 如果当前类加载器有子加载器,则子加载器还会尝试加载,否则就会包 ClassNotFoundException
return loadedClass;
}
双亲委派作用
- 防止类重复加载提高效率
- 保证核心类安全,无法被替换
假设通过网络传递一个名为java.lang.Integer
的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的类,而直接返回已加载过的Integer.class
,这样便可以防止核心API库被随意篡改。
Java 类加载器层级结构
如双亲委派模型图中展示的,Java 中现有类加载器分三层。
-
BootstrapClassLoader
,启动类加载器,顶级类加载器,由 native 实现,加载JAVA_HOME/lib下面的核心类库或-Xbootclasspath选项指定的jar包等虚拟机识别的类
。 -
ExtClassLoader
,拓展类加载器,父加载器是BootstrapClassLoader
,所以parent==null
,加载JAVA_HOME /lib/ext或者由系统变量-Djava.ext.dir指定位置中的类
。 -
AppClassLoader
,系统类加载器,默认的类加载器,也是自定义类加载器的父加载器,加载当前类所在路径及其引用的第三方类。
网友评论