Search code examples
javaclassloaderdeadlock

Java Deadlock in ClassLoaders


I have written two custom class loaders to load code dynamically.

The first one does load code from a Jar:

package com.customweb.build.bean.include;

import java.net.URL;
import java.net.URLClassLoader;

import com.customweb.build.process.ILeafClassLoader;

public class JarClassLoader extends URLClassLoader implements ILeafClassLoader {

    public JarClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    public Class<?> findClassWithoutCycles(String name) throws ClassNotFoundException {

        Class<?> c = findLoadedClass(name);
        if (c != null) {
            return c;
        }

        return findClass(name);
    }

    @Override
    protected Class<?> findClass(String qualifiedClassName) throws ClassNotFoundException {
        synchronized (this.getParent()) {
            synchronized (this) {
                return super.findClass(qualifiedClassName);
            }
        }
    }

    @Override
    public URL findResourceWithoutCycles(String name) {
        return super.findResource(name);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        synchronized (this.getParent()) {
            synchronized (this) {
                return super.loadClass(name);
            }
        }
    }

}

The other class loader takes multiple class loaders to allow to access the classes of the other class loaders. During the initialization of the first one, I set an instance of this class loader as the parent. To break the cycle I use the method 'findClassWithoutCycles'.

package com.customweb.build.process;

import java.net.URL;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.List;

public class MultiClassLoader extends SecureClassLoader {

    private final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();

    public MultiClassLoader(ClassLoader parent) {
        super(parent);
    }

    public void addClassLoader(ClassLoader loader) {
        this.classLoaders.add(loader);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        for (ClassLoader loader : classLoaders) {
            try {
                if (loader instanceof ILeafClassLoader) {
                    return ((ILeafClassLoader) loader).findClassWithoutCycles(name);
                } else {
                    return loader.loadClass(name);
                }
            } catch (ClassNotFoundException e) {
                // Ignore it, we try the next class loader.
            }
        }

        throw new ClassNotFoundException(name);
    }

    @Override
    protected URL findResource(String name) {

        for (ClassLoader loader : classLoaders) {
            URL url = null;
            if (loader instanceof ILeafClassLoader) {
                url = ((ILeafClassLoader) loader).findResourceWithoutCycles(name);
            } else {
                url = loader.getResource(name);
            }

            if (url != null) {
                return url;
            }
        }

        return null;
    }
}

But when I use this class loaders I get most of the time a deadlock. I have past here the thread dump: http://pastebin.com/6wZKv4Y0

Since the Java ClassLoader blocks in some methods the thread by synchronizing on $this, I try to synchronizing on the MultiClassLoader first and then on the JarClassLoader. This should prevent any deadlocks, when the order of acquiring a lock is the same. But it seems as somewhere in the native class loading routine a lock on the class loader is acquired. I came to this conclusion because the thread 'pool-2-thread-8' is locked on the object '0x00000007b0f7f710'. But in the log I can't see when this lock is acquired and by which thread.

How can I find out which thread does the synchronizing on the classloader?

Edit: I solved it by synchronizing on all class loaders before invoking the loadClass of the MultiClassLoader.


Solution

  • The JVM acquires a lock on the ClassLoader before it invokes loadClass. This happens if class loaded via one of your JarClassLoader references another class and the JVM tries to resolve that reference. It will go directly to the ClassLoader which created the class, lock it and invoke loadClass. But then you are trying to get a lock on the parent loader before locking the JarClassLoader again. So the ordering of the two locks does not work.

    But I don’t see reasons for any of the two locks as you do not access any resources requiring synchronization. The inherited internal state of the URLClassLoader is maintained by its implementation itself.

    However if you want to add more state to your classes requiring synchronization you should use different mechanisms as locking the ClassLoader instances.

    http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html says

    If you have a custom class loader with a risk of deadlocking, with the Java SE 7 release, you can avoid deadlocks by following these rules:

    1. Ensure that your custom class loader is multithread safe for concurrent class loading.

      a. Decide upon an internal locking scheme. For example, java.lang.ClassLoader uses a locking scheme based on the requested class name.

      b. Remove all synchronization on the class loader object lock alone.

      c. Ensure that critical sections are safe for multiple threads loading different classes.

    2. In your custom class loader's static initializer, invoke java.lang.ClassLoader's static method registerAsParallelCapable(). This registration indicates that all instances of your custom class loader are multithread safe.

    3. Check that all class loader classes that this custom class loader extends also invoke the registerAsParallelCapable() method in their class initializers. Ensure that they are multithread safe for concurrent class loading.

    If your custom class loader overrides only findClass(String), you do not need further changes. This is the recommended mechanism to create a custom class loader.