Concurrency control lock on class loading (ClassLoadingLock)

Deadlock

Before JDK1.7, some core methods of java.lang.ClassLoader were modified by synchronized, such as loadClass. The following is an excerpt from some methods of java.lang.ClassLoader under JDK6:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {...}

private synchronized Class loadClassInternal(String name) throws ClassNotFoundException {...}

private synchronized void checkCerts(String name, CodeSource cs) {...}

private static synchronized void initSystemClassLoader() {...}

Under the traditional parental delegation model, there is no problem using synchronized to do concurrency control, but if there is a situation where the class loading is dependent on each other, it is prone to deadlock. A typical example is that multiple modules of OSGI depend on the package released by each other. When a module needs to load a dependent package, it needs to be delegated to the module class loader that released the package to load. For an introduction to OSGI, please refer to: "In-depth understanding of the Java virtual machine" reading notes (eight)-class loading and execution subsystem cases (Tomcat class loading, OSGI, dynamic proxy) . If the following situations occur:

  • BundleA: release packageA, depend on packageB

  • BundleB: release packageB, depend on packageA

Because the method is modified by synchronized, when BundleA loads PackageB, it must first lock the instance object of BundleA class loader, and then delegate the request to the class loader of BundleB. But if BundleB also just wants to load the class of packageA, then It is necessary to lock the BundleB class loader before requesting the BundleA loader to process, so that both loaders are waiting for the other to process their own requests, but each holds its own lock, causing a deadlock state.

BundleA and BundleB depend on each other

 solve

After JDK1.7, this problem has been optimized. The class loading can be marked as having parallel capability by manual registration. Later, when loading classes, the method-level synchronized method is abandoned and changed to be loaded for each The class file (full path name) corresponds to an Object object lock. When locking, if the loader has parallel capabilities, then the class loader will no longer be locked, but the Object lock corresponding to the loaded class file will be found for the lock operation. Reference: http://openjdk.java.net/projects/jdk7/features/#f352

For example, register like this:

static {
		ClassLoader.registerAsParallelCapable();
	}

registerAsParallelCapable is a static method of ClassLoader, which will call ParallelLoaders.register method, ParallelLoaders is a static internal class of ClassLoader, which contains a static property of type Set, the above registration operation will add the class loader to it:

//Set类型,存放具备并行能力的类加载器
private static final Set<Class<? extends ClassLoader>> loaderTypes =
            Collections.newSetFromMap(
                new WeakHashMap<Class<? extends ClassLoader>, Boolean>());

//将类加载器注册到loaderTypes中
static boolean register(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                if (loaderTypes.contains(c.getSuperclass())) {
                    loaderTypes.add(c);
                    return true;
                } else {
                    return false;
                }
            }
        }

When the class loader is instantiated, the constructor of the parent class ClassLoader (all custom class loaders will directly or indirectly inherit from ClassLoader) will determine whether the current loader has parallel capabilities (whether it is in loaderTypes), if so, Then initialize a non-static property parallelLockMap of CurrentHashMap type to store the lock object, the key is the name of the bytecode file to be loaded, and the value is the Object lock object:

private final ConcurrentHashMap<String, Object> parallelLockMap;
private ClassLoader(Void unused, ClassLoader parent) {
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
        }
    }

In the method definitions that need to be locked in ClassLoade, the synchronized modification is removed, and the lock is acquired in the method and then synchronized. Take loadClass as an example:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {...}
    }

Focus on the getClassLoadingLock method:
 

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;
    }

It can be seen that if the class loader has parallel capabilities and a parallelLockMap is created, an Object lock object will be created for each className, otherwise the class loader itself will be used.

 

Guess you like

Origin blog.csdn.net/huangzhilin2015/article/details/114873607