0. 前言
尽管两个类来源于同一个 Class 文件,被同一个虚拟机加载,但是加载他们的类加载器不同时,那这两个类就不相等
1. 双亲委派模型
1.1 系统提供的3种类加载器
- 启动类加载器(Bootstrap ClassLoader):加载
%JRE_HOME%\lib
目录下的类,例如 java.lang.* - 扩展类加载器(Extension ClassLoader):加载
%JRE_HOME%\lib\ext
目录下的类 - 应用程序类加载器(Application ClassLoader):加载用户类路径(classpath)下的类
如果有需要的可以加入自定义类加载器
1.2 工作过程
一个类加载器收到类加载的请求时,会把这个请求委派给父类加载器,每个层次的类加载器都是如此,因此最终请求传送到启动类加载器,只有当父类加载器表示自己无法完成该类的加载(它的搜索范围中没有找到所需类),子加载器才会尝试自己加载
1.3 源码分析
// java.lang.ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 首先,检查类是否加载过
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 让父加载器加载
c = parent.loadClass(name, false);
} else {
// 父加载器不存在,说明到了启动类加载器,由启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 父类加载器无法加载时,由自己加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
1.4 流程图
1.5 作用
- 避免类重复加载
- 保证基础类在各环境中都是同一个类,避免自定义覆盖基础类时出现安全问题
2 破坏双亲委派模型
- 涉及 SPI 的(JNDI、JDBC、JCE、JAXB、JBI 等)基本都是采用线程上下文类加载器(Thread Context ClassLoader)加载,也就是父类加载器通过子类加载器完成类加载,破坏了双亲委派模型
- 代码热替换(HotSwap)、模块热部署(Hot Deployment),例如 OSGI
学自《深入理解Java虚拟机》(第二版)