「JVM 类加载子系统」系列文章
在正式介绍双亲委派模型之前,要思考两个问题,为什么要研究类加载的过程?为什么要研究双亲委派模型?
研究类加载的过程是为了知道加载过程使用到了双亲委派模型,但仅仅知道使用了双亲委派模型还不够,最终目的是要弄清楚为什么要使用双亲委派模型?!双亲委派模型的原理?!双亲委派模型背后的逻辑思想?!该思想是否可以被我们借鉴,为我所用?!
比如:双亲委派模型避免了类的重复加载,避免了核心类库被修改。那么我们在设计框架时,框架底层的内容要不容易被篡改,或者不被攻击,这个时候就可以借鉴双亲委派模型!!
通过上一部分输出的文件目录可以看出,「应用程序类加载器」加载的文件包含了「启动类加载器」加载的文件和「扩展类加载器」加载的文件,那这是不是意味着重复加载了呢?!
其实不然,根据前文 类加载器 的介绍「应用程序类加载器」主要负责加载用户类路径 (ClassPath) 上所有的类库,也就是对应输出中的../../../../concurrency/target/classes
之所以不会重复加载,完全是因为双亲委派模型,它分为两个过程:
注意:双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是继承来实现的,而是通常使用组合关系来复用父加载器的代码
工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求 (它的搜索范围中没有找到所需的类) 时,子加载器才会尝试自己去完成加载
1. 可以避免类重复加载
2. 可以防止核心 API 库被随意修改
java.lang.Object
,它存放在rt.jar
之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object
类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object
的类,并放在程序的ClassPath
中,那系统中就会出现多个不同的Object
类,Java 类型体系中最基础的行为也就无从保证,应用程序将变得一片混乱rt.jar
类库中已有类重名的 Java 类,将会发现它可以正常编译,但永远无法被加载运行3. 全盘委托机制
关于三个类加载器的源码分析可见 剖析 [Bootstrap、Extension、Application] ClassLoader
C++ 语言调用了sun.misc.Launcher.getLauncher()
获取了 launcher 对象,Launcher 类初始化的时候其构造器创建了 ExtClassLoader 和 AppClassLoader,然后接下来调用 launcher 对象的getClassLoader()
方法
xxxxxxxxxx
public ClassLoader getClassLoader() {
return loader;
}
调用getClassLoader()
方法获得了loader
对象,而loader
对象在构造函数中被赋值
xxxxxxxxxx
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
// 获取扩展类加载器,getExtClassLoader() 见下方
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
// 获取应用程序类加载器
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// 省略其它代码 ...
}
类加载器通过调用loader.loadClass("com.lfool.My")
来加载类,根据上面的分析,可以知道双亲委派模型的起点是AppClassLoader
,也正如前文 类加载器 所说:如果应用中没有自定义类加载器,一般情况下使用的就是 AppClassLoader 作为默认类加载器
下图分别是 AppClassLoader 和 ExtClassLoader 的关系图 (IDEA 快捷键:Option + Command + U)
向上委托查找,向下加载
// AppClassLoader 类
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
// 如果类已经加载
if (ucp.knownToNotExist(name)) {
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
// 如果类没有被加载,调用父类的 loadClass() 方法
// AppClassLoader 的父类是 URLClassLoader,但是 URLClassLoader 中没有 loadClass() 方法
// 继续往上看,URLClassLoader 的父类是 ClassLoader,有 loadClass() 方法
// 所以 此处调用的是 ClassLoader 中的 loadClass() 方法,具体见下方
return (super.loadClass(name, resolve));
}
// ClassLoader 类
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// synchronized 同步锁,所以类加载是线程安全的
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { // 父类加载器不为 null,委托给父类加载器加载
c = parent.loadClass(name, false);
} else { // 父类加载器为 null,委托给启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出 ClassNotFoundException
// 说明父类加载器无法完成加载请求
}
// 到这一步 c 还为 null,表示父类加载器无法加载该类
if (c == null) {
// 在父类加载器无法加载时,再调用本身的 findClass 方法来进行类加载
long t1 = System.nanoTime();
// 由于 ClassLoader 类中 findClass 是个抽象方法,需要回到实现了该方法的类中找,回到 URLClassLoader 中,具体见下方
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;
}
}
// URLClassLoader 类
// 根据位置和名称加载 class 字节码
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
// doPrivileged 是一个权限校验的操作,可以先不管
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// com.lfool.My -> com/lfool/My.class,「全限定类名」转化成「类路径」
String path = name.replace('.', '/').concat(".class");
// 去 resource 库中找这个路径,没有就返回 null
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 找到了,开始执行加载、验证、准备、解析、初始化,具体见下方
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
} catch (ClassFormatError e2) {
if (res.getDataError() != null) {
e2.addSuppressed(res.getDataError());
}
throw e2;
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
// 将字节码转化为 Class 对象
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
// 获取 classes 目录的绝对路径,如:file:/Users/lfool/myself/IdeaProjects/concurrency/target/classes
URL url = res.getCodeSourceURL();
if (i != -1) {
// 获取包名
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// 读入 class 的字节流
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
// 见下方
return defineClass(name, b, 0, b.length, cs);
}
}
// ClassLoader 类
// 通常是一些基础的校验,比如准备阶段,解析阶段,初始化阶段都是本地方法
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
// 预定义类信息
protectionDomain = preDefineClass(name, protectionDomain);
// 定义类源码
String source = defineClassSourceLocation(protectionDomain);
// 初始化类,调用本地方法
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
// 类定义后置处理
postDefineClass(c, protectionDomain);
return c;
}