概述
Java 虚拟机设计团队有意将类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到 Java 虚拟机外部来实现,以便让应用程序自己来决定如何去获取所需的类,实现这个动作的代码称之为"类加载器 (Class Loader) "。
由于 JDK9 引入了模块化新特性,所以 JDK9 前后的类加载实现也略有区别,本文将分开讲解。
首先基于 JDK8 来讲解类加载机制。
JDK8
双亲委派模型
java.lang.ClassLoader 抽象类的 loaderClass() 方法定义了类加载的流程:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// loadClass() 方法是通过 sycchronized 关键字来保证线程安全性的
// 每个 class name 会对应1个锁
// 若多个线程同时加载 class name 相同的类,因为锁的存在,仅有1个线程会获得执行
// 若多个线程同时加载 class name 不同的类,因为各线程持有的锁是不同的,则线程间互不影响
synchronized (getClassLoadingLock(name)) {
// 首先检查一下该类是否已被加载过,若被加载过,则直接返回,避免重复加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 若父加载器存在,则由父加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 若父加载器不存在,则由 Bootstrap ClassLoader 加载
// 进入到这个分支,说明父加载器为 null
// 父加载器为 null 的 ClassLoader 为扩展类加载器 (Extension ClassLoader)
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 若父加载器和 Bootstrap ClassLoader 均加载失败
// 则调用当前类加载器的 findClass()方法
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;
}
}
// 获取 class name 对应的类加载器锁
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
上述类加载过程即为 Java 的双亲委派模型,其工作过程是: 若一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一个层次的类加载器均是如此,因此所有的加载请求最终都应该传递到最顶层的启动类加载器中,只有当父加载器反馈无法自己完成这个加载请求(它的搜索范围内没有找到所需的类)时,子加载器才会尝试自己完成加载。
使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处是 Java 中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如 java.lang.Object,它存在于 rt.jar 之中,无论哪一个类加载器要加载这个类,最终都会委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都能够保证是同一个类。
反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了1个名为 java.lang.Object 的类,并放在程序的 classPath 中,那系统中就会存在多个不同的 Object 类,Java 类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。
同时,loadClass() 方法使用 sycchronized 关键字来保证线程安全性,并维护了1个 ConcurrentHashMap<String, Object> parallelLockMap 来保存各个 class name 对应的 Object 锁,每个 class name 均对应1个 Object 锁的好处是,仅有加载同一个 class 的线程会互斥,加载不同 class 的线程不会互斥。试想一下,如果只传入1个 Object 作为公共锁,则所有执行 loadClass() 方法的线程在同一时刻仅能有1个获得执行,加载不同 class 的线程也会因此互斥(但该种情况是无需互斥的),造成方法执行效率低下。
由 loadClass() 方法的执行逻辑可知,类加载请求最终都会走到 findBootstrapClassOrNull() 方法,最先由启动类加载器 (Bootstrap ClassLoader) 来尝试类加载。
启动类加载器 (Bootstrap ClassLoader)
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
可以看到,findBootstrapClass() 方法是1个 native 方法,所以启动类加载器 (Bootstrap ClassLoader) 是由 C++来实现的。
启动类加载器 (Bootstrap ClassLoader) 负责加载存放在 <JAVA_HOME>lib 目录,或者被 -Xbootclasspath 参数所指定的路径中存放的,而且是 Java 虚拟机能够识别的(按照文件名识别,如 rt.jar、tools.jar,名字不符合的类库即使放在 lib 目录下也不会被加载)类库加载到虚拟机的内存中。
接着看一下 ClassLoader 抽象类的继承子类:
扩展类加载器 (Extension ClassLoader)
先看一下 ExtClassLoader 继承类:
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
// 双重校验创建 ExtClassLoader 单例
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
// 最终通过构造器方法创建 ExtClassLoader 实例
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
void addExtURL(URL var1) {
super.addURL(var1);
}
public ExtClassLoader(File[] var1) throws IOException {
// 可以看到,ExtClassLoader 的父加载器为 null
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
private static File[] getExtDirs() {
// 扫描系统变量 java.ext.dirs 指定的路径
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
...
}
可以看出,扩展类加载器 (Extension ClassLoader) 是 sun.misc.Launcher 的静态内部类,其负责加载 <JAVA_HOME>libext 目录中或者系统变量 java.ext.dirs 指定的路径下的所有类库。根据"扩展类加载器"这个名称就可以推断出这是一种 Java 系统类库的扩展机制,JDK 的开发团队允许用户将具有通用性的类库放置在 ext 目录下以扩展 Java SE 的功能。
在 JDK9 以后,这种扩展机制被模块化带来的天然扩展能力所取代。由于扩展类加载器是由 Java 代码实现的,开发者可以直接在程序中使用扩展类加载器来加载 Class 文件。
因为启动类加载器 (Bootstrap ClassLoader) 不是由 Java 代码实现的,所以扩展类加载器 (Extension ClassLoader) 的父加载器并不是启动类加载器,而是 null。也就是说,在 Java 这个地盘上,扩展类加载器 (Extension ClassLoader) 就是祖师爷。
应用程序类加载器 (Application ClassLoader)
接着看一下应用程序类加载器 (Application ClassLoader):
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
// 这里的 var0 就是传入的 AppClassLoader 的父加载器
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
// 虽然复写了 loadClass() 方法,但本质上还是调用的 ClassLoader 类的 loadClass() 方法
return super.loadClass(var1, var2);
}
}
接着看一下,何处调用了 getAppClassLoader() 方法,以确定 AppClassLoader 的父加载器是谁?
由上图可知,Launcher 构造函数中调用了 getAppClassLoader() 方法,同时指定 ExtClassLoader 类作为 AppClassLoader 的父加载器。
那 Launcher 又是在何处实例化呢?
根据 Launcher 类的构造方法,其 getClassLoader() 方法的返回值就是 ApplicationClassLoader。
综上,应用程序类加载器 (Application ClassLoader) 是sun.misc.Launcher 的静态内部类,其负责加载用户类路径 (ClassPath) 上所有的类库,它的父加载器为 ExtClassLoader。同时应用程序类加载器是 ClassLoader 类中的 getSystemClassLoader() 方法的返回值,所以有些场合也称它为"系统类加载器"。
自定义类加载器 (Custom ClassLoader)
根据 ClassLoader 的 loadClass() 方法,如果想实现自己的类加载逻辑,可以继承 ClassLoader 类,并覆写 findClass() 方法,按照 loadClass() 方法的逻辑,如果父加载器加载失败,会自动调用自身的 findClass() 方法来完成加载,这样即不影响用户按照自己的意愿去加载类,又可以保证新写出来的类加载器是符合双亲委派规则的。
因为自定义类加载器 (Custom ClassLoader) 是继承自 ClassLoader 的,其创建实例的时候,必然先伴随着父类 ClassLoader 的创建。
// 构造函数会调用 getSystemClassLoader() 方法指定父加载器
// getSystemClassLoader() 方法的返回值为 ApplicationClassLoader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
综上,可以得出类加载器间的关系:
- 启动类加载器 (Bootstrap ClassLoader) 由 C++ 实现,无父加载器;
- 扩展类加载器 (Extension ClassLoader) 由 Java 实现,父加载器为 null;
- 应用程序类加载器 (Application ClassLoader) 由 Java 实现,父加载器为 Extension ClassLoader;
- 自定义类加载器 (Custom ClassLoader) 由用户基于 Java 实现,父加载器为 Application ClassLoader。
需要注意的是,上述类加载器之间的父子关系不是以继承 (Inheritance) 的关系来实现的,而是使用组合 (Composition) 关系来复用父加载器的代码。
讲完了 JDK8 版本,接着基于 JDK11 来讲解类加载机制。
JDK11
在 JDK9 中引入了 Java 模块化系统 (Java Platform Module System, JPMS) 是对 Java 技术的一次重要升级,为了能够实现模块化的关键目标–可配置的封装隔离机制,Java 虚拟机对类加载架构也做出了对应的变动调整,才能使模块化系统得以顺利地运行。
先看一下 ClassLoader 类的继承子类:
启动类加载器 (Boot ClassLoader)
private static class BootClassLoader extends BuiltinClassLoader {
// 其父加载器为 null
BootClassLoader(URLClassPath bcp) {
super(null, null, bcp);
}
@Override
protected Class<?> loadClassOrNull(String cn) {
return JLA.findBootstrapClassOrNull(this, cn);
}
};
平台类加载器 (Platform ClassLoader)
private static class PlatformClassLoader extends BuiltinClassLoader {
static {
if (!ClassLoader.registerAsParallelCapable())
throw new InternalError();
}
// Platform ClassLoader 的父加载器为 BootClassLoader
PlatformClassLoader(BootClassLoader parent) {
super("platform", parent, null);
}
private Package definePackage(String pn, Module module) {
return JLA.definePackage(this, pn, module);
}
}
应用程序类加载器 (Application ClassLoader)
private static class AppClassLoader extends BuiltinClassLoader {
static {
if (!ClassLoader.registerAsParallelCapable())
throw new InternalError();
}
final URLClassPath ucp;
// AppClassLoader 的父加载器为 PlatformClassLoader
AppClassLoader(PlatformClassLoader parent, URLClassPath ucp) {
super("app", parent, ucp);
this.ucp = ucp;
}
@Override
protected Class<?> loadClass(String cn, boolean resolve)
throws ClassNotFoundException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = cn.lastIndexOf('.');
if (i != -1) {
sm.checkPackageAccess(cn.substring(0, i));
}
}
// 调用父类 BuiltinClassLoader 的 loadClass() 方法
return super.loadClass(cn, resolve);
}
...
}
接着看一下 BuiltinClassLoader 的 loadClass() 方法:
@Override
protected Class<?> loadClass(String cn, boolean resolve)
throws ClassNotFoundException
{
Class<?> c = loadClassOrNull(cn, resolve);
if (c == null)
throw new ClassNotFoundException(cn);
return c;
}
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
synchronized (getClassLoadingLock(cn)) {
// check if already loaded
Class<?> c = findLoadedClass(cn);
if (c == null) {
// 判断该类归属于哪个系统模块
LoadedModule loadedModule = findLoadedModule(cn);
// 如果该类有指定模块
if (loadedModule != null) {
// package is in a module
BuiltinClassLoader loader = loadedModule.loader();
if (loader == this) {
if (VM.isModuleSystemInited()) {
c = findClassInModuleOrNull(loadedModule, cn);
}
} else {
// delegate to the other loader
c = loader.loadClassOrNull(cn);
}
// 如果该类没有指定模块
} else {
// 若存在父加载器,则交由父加载器负责加载
if (parent != null) {
c = parent.loadClassOrNull(cn);
}
// check class path
if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
c = findClassOnClassPathOrNull(cn);
}
}
}
if (resolve && c != null)
resolveClass(c);
return c;
}
}
自定义类加载器 (Custom ClassLoader)
因为自定义类加载器 (Custom ClassLoader) 是继承自 ClassLoader 的,其创建实例的时候,必然先伴随着父类 ClassLoader 的创建。
protected ClassLoader() {
this(checkCreateClassLoader(), null, getSystemClassLoader());
}
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
switch (VM.initLevel()) {
case 0:
case 1:
case 2:
// the system class loader is the built-in app class loader during startup
return getBuiltinAppClassLoader();
case 3:
String msg = "getSystemClassLoader cannot be called during the system class loader instantiation";
throw new IllegalStateException(msg);
default:
// system fully initialized
assert VM.isBooted() && scl != null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
}
// 故自定义类加载器的父加载器为 AppClassLoader
static ClassLoader getBuiltinAppClassLoader() {
return ClassLoaders.appClassLoader();
}
综上,可以得出类加载器间的关系:
- 启动类加载器 (Boot ClassLoader) 由 Java 实现,父加载器为 null;
- 平台类加载器 (Platform ClassLoader) 由 Java 实现,父加载器为 Boot ClassLoader;
- 应用程序类加载器 (Application ClassLoader) 由 Java 实现,父加载器为 Platform ClassLoader;
- 自定义类加载器 (Custom ClassLoader) 由用户基于 Java 实现,父加载器为 Application ClassLoader。
自定义类加载器 (Custom ClassLoader) 加载类时,会调用 ClassLoader 的 loadClass() 方法,由于双亲委派机制,向上传导给其父加载器 Application ClassLoader。
父加载器 Application ClassLoader 覆写了 loadClass() 方法,然后转到其父类 BuiltinClassLoader 的 loadClass() 方法,进而调用 BuiltinClassLoader 的 loadClassOrNull() 方法。
BuiltinClassLoader 的 loadClassOrNull() 方法也是逐级往上抛,交由其父加载器先尝试类加载。
逐步传导到 Boot ClassLoader,然后调用其 loadClassOrNull() 方法:
@Override
protected Class<?> loadClassOrNull(String cn) {
return JLA.findBootstrapClassOrNull(this, cn);
}
// 最终是通过 JavaLangAccess 来实现类的加载
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
需要注意的是: JDK11 中的启动类加载器 (Boot ClassLoader) 和 JDK8 里面的启动类加载器 (Bootstrap ClassLoader) 不是一个东西。
不知不觉,越写越多,破坏双亲委派模型的案例还未讲解,后面专门再写一篇来讲吧。
本文到此结束,感谢阅读!
最后
以上就是大意舞蹈为你收集整理的基于源码深入了解Java的类加载机制(JDK8和JDK11双版本)的全部内容,希望文章能够帮你解决基于源码深入了解Java的类加载机制(JDK8和JDK11双版本)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复