自定义ClassLoader不是垃圾回收

时间:2013-01-01 19:36:26

标签: memory-leaks garbage-collection classloader openjpa glassfish-2.x

为了解决this problem,我构建了一个(非常)小的project,它正在复制它的一部分。它是一个使用Glassfish v2.1.1和OpenJpa-1.2.2的NetBeans项目。

在全球范围内,目标是能够动态重新加载一些业务代码(称为“任务”),而无需(重新)进行完整部署(例如通过asadmin)。在项目中有两个:PersonTask和AddressTask,它们只是访问一些数据并打印出来。

为了做到这一点,我实现了一个自定义类加载器,它读取类文件的二进制文件并通过defineClass方法注入它。基本上,这个CustomClassLoader是一个单例,实现方式如下:

public class CustomClassLoader extends ClassLoader {
    private static CustomClassLoader instance;
    private static int staticId = 0;
    private int id; //for debugging in VisualVM
    private long threadId; //for debugging in VisualVM

    private CustomClassLoader(ClassLoader parent) {
        super(parent);
        threadId = Thread.currentThread().getId();
        id = staticId;
        ++staticId;
    }

    private static CustomClassLoader getNewInstance() {
        if (instance!=null) {
            CustomClassLoader ccl = instance;
            instance = null;

            PCRegistry.deRegister(ccl); //https://issues.apache.org/jira/browse/GERONIMO-3326
            ResourceBundle.clearCache(ccl); //found some references in there while using Eclipse Memory Analyzer Tool
            Introspector.flushCaches(); //http://java.jiderhamn.se/category/classloader-leaks/

            System.runFinalization();
            System.gc();
        }

        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        instance = new CustomClassLoader(parent);
        return instance;
    }

    //...
 }
//this class is included in the EAR like a normal class
public abstract class AbstractTask {
    protected Database database; /* wrapper around the EntityManager, filled when instance is created */
    public abstract void process(Integer id);
}

//this one is dynamically loaded by the CustomClassLoader
public class PersonTask extends AbstractTask {
    @Override
    public void process(Integer id) {
        //keep it empty for now
    }
}

在我的EJB外观(EntryPointBean)中,我只是查找该类,创建它的新实例并在其上调用process方法。项目中的代码略有不同,但想法完全相同:

CustomClassLoader loader = CustomClassLoader.getNewInstance();
Class<?> clazz = loader.loadClass("ch.leak.tasks.PersonTask");
Object instance = clazz.newInstance();
AbstractTask task = (AbstractTask)instance;

/* inject a new Database instance into the task */

task.process(...);

到现在为止,一切都很好。如果此代码多次运行(通过ch.leak.test.Test),则在完成堆分析时将只有一个CustomClassLoader实例,这意味着先前的实例已成功收集。

现在,这是触发泄漏的行:

public class PersonTask extends AbstractTask {
    @Override
    public void process(Integer id) {
        Person p = database.getEntity("SELECT p FROM Person p WHERE p.personpk.idpk=?1", new Long(id));
        //...
    }
}

这种对数据库的简单访问有一个奇怪的结果:代码运行的第一次时间,正在使用的CustomClassLoader永远不会被垃圾收集(即使没有任何GC根)。但是,创建的所有其他CustomClassLoader都不会泄漏。

正如我们在下面的转储中看到的那样(使用VisualVM完成),实例ID为0的CustomClassLoader永远不会被垃圾收集... CustomClassLoader not garbage collected

最后,我在探索堆转储时看到的另一件事:我的实体在PermGen中被声明两次,其中一半没有实例,也没有GC根(但它们没有链接到CustomClassLoader)。 enter image description here

似乎OpenJPA与这些漏洞有关......但我不知道在哪里可以搜索更多关于我做错的信息。我还将堆转储直接放在项目的zip中。 有没有人有想法?

谢谢!

0 个答案:

没有答案