ClassLoaders中的Java死锁

Tho*_*ker 6 java deadlock classloader

我编写了两个自定义类加载器来动态加载代码.

第一个从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);
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

另一个类加载器需要多个类加载器才能访问其他类加载器的类.在第一个初始化期间,我将此类加载器的实例设置为父类.为了打破循环,我使用方法'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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是当我使用这个类加载器时,我大部分时间都陷入僵局.我已经过去了线程转储:http: //pastebin.com/6wZKv4Y0

由于Java ClassLoader在某些方法中通过同步$ this来阻塞线程,我首先尝试在MultiClassLoader上进行同步,然后在JarClassLoader上进行同步.当获取锁的顺序相同时,这应该可以防止任何死锁.但它似乎在本机类加载例程中的某个地方获得了对类加载器的锁定.我得出了这个结论,因为线程'pool-2-thread-8'被锁定在对象'0x00000007b0f7f710'上.但是在日志中我无法看到何时获取此锁以及哪个线程.

如何找出类加载器上的同步哪个线程?

编辑:我通过在调用MultiClassLoader的loadClass之前同步所有类加载器来解决它.

Hol*_*ger 5

JVM在调用loadClass之前获取对ClassLoader的锁定.如果通过您的某个JarClassLoader加载的类引用另一个类并且JVM尝试解析该引用,则会发生这种情况.它将直接转到创建类的ClassLoader,锁定它并调用loadClass.但是,在再次锁定JarClassLoader之前,您正试图锁定父加载器.所以两个锁的顺序不起作用.

但是我没有看到任何两个锁的原因,因为您没有访问任何需要同步的资源.URLClassLoader的继承内部状态由其实现本身维护.

但是,如果要向需要同步的类添加更多状态,则应使用不同的机制来锁定ClassLoader实例.

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

如果你有一个具有死锁风险的自定义类加载器,使用Java SE 7发行版,你可以遵循以下规则来避免死锁:

  1. 确保您的自定义类加载器对于并发类加载是多线程安全的.

    一个.决定内部锁定方案.例如,java.lang.ClassLoader使用基于所请求的类名的锁定方案.

    仅删除类装入器对象锁上的所有同步.

    C.确保关键部分对于加载不同类的多个线程是安全的.

  2. 在自定义类加载器的静态初始化程序中,调用java.lang.ClassLoader的静态方法registerAsParallelCapable().此注册表明您的自定义类加载器的所有实例都是多线程安全的.

  3. 检查此自定义类加载器扩展的所有类加载器类是否也在其类初始值设定项中调用registerAsParallelCapable()方法.确保它们对于并发类加载是多线程安全的.

如果您的自定义类加载器仅覆盖findClass(String),则不需要进一步更改.这是创建自定义类加载器的推荐机制.