2013-09-05 59 views
5

我写了两个自定义类加载器来动态加载代码。类加载器中的Java死锁

第一个不负载代码从一个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

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

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

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

+0

为什么要在父类加载器上同步?我没有看到任何理由。 – Holger

+0

在类的定义过程中,ClassLoader被用作Lock(同步(this))。当一个调用在MultiClassLoader中完成loadClass()时,这个ClassLoader也被同步。意思是:有些情况下,在JarClassLoader同步之前,父(MultiClassLoader)是同步的。在这种情况下,你会输入一个死锁。通过同步你可以避免这种僵局。 –

+0

我看不出如何添加更多同步应该能够避免死锁。你的问题证明这个概念是行不通的。不过,我希望我的回答会有所帮助。 – Holger

回答

4

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. 确保您的自定义类加载器对于并发类加载是多线程安全的。

    a。 决定内部锁定方案。例如,java.lang.ClassLoader使用基于请求的类名称的锁定方案。

    b。单独删除类加载器对象锁上的所有同步

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

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

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

如果您的自定义类装载器只覆盖的findClass(字符串),则不需要进一步的更改。这是建立自定义类加载器的推荐机制。

+0

@Holgar:你的回答非常有帮助。加载类时,我不知道JVM在类加载器上锁定。但是使用registerAsParallelCapable()并不成功。在调用loadClass()之前,我锁定了MultiClassLoader中的所有类加载器。这保留了获取锁的顺序。 –