Preface
上回书说道,我们在java.lang.ClassLoader中的loadClass方法中看到有类加载相关的并发控制,那么它到底是为了解决什么样的问题,以及它是如何使用的呢?
LoadClassMethod
在jdk1.7以前,java.lang.ClassLoader的一些核心的方法都是被synchronized修饰的,比如像如下的loadClass方法。
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//.....
}
而在jdk1.8中,java.lang.ClassLoader对一些核心的方法进行了优化,其代码如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//....
}
}
Why change?
在上文我们所讲到的"双亲委派"的机制下,其层级都是树形的,你可能听说过Tomcat6之前多层级类加载器。但是它其实也按照"双亲委派"的机制实现的(它也可以打破"双亲委派"机制,关键点在于WebAppClassLoader,本篇不展开讨论,详情见笔者<<Tomcat如何打破双亲委派机制>>),如下所示。
但是也有例外的场景,就拿OSGI(Open Service Gateway Initiative)来说,它是一个基于Java平台的动态模块化规范,OSGI中的每个模块(Bundle)可以声明所依赖的JavaPackage,也可以声明它允许导出发布的JavaPackage,每个模块从表面看来为平级之间的依赖。OSGI其中一个很大的优点就是基于OSGI的程序很可能可以实现模块级别的热插拔功能,其背后所对应的就是它灵活的类加载器的架构。
而OSGI采取了不同的类加载机制保证每个模块都有自己独立的classpath,它为每个Bundle提供一个类加载器,该加载器能够看到Bundle内部的元数据资源。Bundle可以基于依赖关系互相协作,可以从一个Bundle类加载器委托到另一个Bundle类加载器。
例如BundleA依赖Java的核心类库,它委托给BootstrapClassLoader类加载器。而BundleB依赖BundleA、BundleC、核心类库,BundleC依赖BundleA,那么它们的关系图如下:
为什么花大篇幅说明这个呢?且听继续讲解,从上文我们可以看到,OSGI不在是"双亲委派"的树形的加载机制,而是演进为一种在运行时的更为复杂的"网格委派"机制,通过上文得知,在jdk1.7以前loadClass等核心方法都是被synchronized修饰的,所以就会产生一个问题,当BundleA依赖BundleB,并且BundleB依赖BundleA的时候,BundleA锁定当前类加载对象,然后委派给BundleB。BundleB锁定当前类加载器对象,然后委派给BundleA,就造成了死锁,如下。
ClassLoadingLock
所以说,在jdk1.7中针对此上的网状型类加载结构做了优化,就是本文所要讲的ClassLoadingLock。其通过手动注册的方式将classLoader标识为具有并行能力。其后在loadClass的时候废弃方法级别synchronized的方式,改为了为每个要加载的class文件(全限定名)都对应一个Object对象锁。而当加锁的时候如果此类加载器具有并行能力,那么就不再对这个类加载器进行加锁,而是找到这个类所对应的Object对象进行加锁操作。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 类加载的并发控制锁
synchronized (getClassLoadingLock(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;
}
AppClassLoader
这里拿AppClassLoader来举例说明,其依赖关系如下。
静态方法注册AppClassLoader为并行处理类
static class AppClassLoader extends URLClassLoader {
static {
// 注册此类加载器为并行处理
ClassLoader.registerAsParallelCapable();
}
//....
}
而ClassLoader.registerAsParallelCapable()是ClassLoader的静态方法,其中会调用ParallelLoaders.register方法,ParallelLoaders是ClassLoader的一个静态内部类,其中包含一个Set类型的静态属性,上面的注册操作会把类加载器添加到其中:
@CallerSensitive
protected static boolean registerAsParallelCapable() {
// 获取调用者类,并判断是否是ClassLoader的子类
Class<? extends ClassLoader> callerClass =
Reflection.getCallerClass().asSubclass(ClassLoader.class);
// 将调用者类进行注册
return ParallelLoaders.register(callerClass);
}
// 存放具备并行能力的类加载器容器
private static final Set<Class<? extends ClassLoader>> loaderTypes =
Collections.newSetFromMap(
new WeakHashMap<Class<? extends ClassLoader>, Boolean>()
);
// 具体的注册方法
static boolean register(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
if (loaderTypes.contains(c.getSuperclass())) {
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
到此AppClassLoader通过静态方法注册到loaderTypes中,而java的类加载过程中如果父类有静态代码块的话,先执行父类的静态代码块,再执行子类的静态代码块,然后执行父类的无参构造,再执行子类的构造方法。其顶层父类的无参构造如下:
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
// 判断是否注册为具有并行处理能力的类加载器
if (ParallelLoaders.isRegistered(this.getClass())) {
// 用于存放锁对象,key:需要加载的字节码文件名。value:Object锁对象:
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
assertionLock = this;
}
}
static boolean isRegistered(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
return loaderTypes.contains(c);
}
}
到这里,为什么loadClass中调用getClassLoadingLock(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;
}
小结
经过此上分析,我们不难得出,如果某一个类加载器注册了具备并行能力,其会在初始化的时候创建了parallelLockMap用来存储类的全限定名与锁对象,也就是说会为每一个对应的className都创建一个Object锁对象,如果没有注册为并行处理的类加载器,其还是使用类加载器本身。