6

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

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

}

另一个类加载器需要多个类加载器来允许访问其他类加载器的类。在第一个初始化期间,我将这个类加载器的一个实例设置为父级。为了打破循环,我使用了“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;
    }
}

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

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

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

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

4

1 回答 1

7

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),则不需要进一步更改。这是创建自定义类加载器的推荐机制。

于 2013-09-05T17:39:33.293 回答