基于源码深入了解Java的类加载机制(JDK8和JDK11双版本)

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>\lib\ext 目录中或者系统变量 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) 不是一个东西。

不知不觉,越写越多,破坏双亲委派模型的案例还未讲解,后面专门再写一篇来讲吧。

本文到此结束,感谢阅读!

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值