即使没有GC根,类加载器也不会被垃圾收集

时间:2012-12-08 15:18:41

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

我们在Glassfish V2.1.1下运行了一个复杂的应用程序。为了能够动态加载我们的代码,我们实现了一个CustomClassloader,它能够重新定义类。 行为非常简单:当动态加载的类发生更改时,CustomClassloader的当前实例将被“删除”,并创建一个新实例以重新定义所需的类。

这很有效,只是在经过几次重新加载相同的类之后(因此每次创建一个新的CustomClassloader),我们得到一个PermGen空间错误,因为CustomClassloader的其他实例不是垃圾收集的。 (这个类应该只有一个实例)

我尝试了不同的方法来追踪泄漏的位置:

  1. visualvm =>我进行堆转储并提取CustomClassloader的所有实例。我可以看到他们都没有最终确定。当我检查最近的GC根时,visualvm告诉我没有(除了最后一个实例,因为它是'真实'使用过的。)
  2. jmap/jhat =>它给了我几乎相同的结果:我看到了CustomClassloader的所有实例,然后当我点击链接以查看其中一个的引用位置时,我得到一个空白页面意味着没有...
  3. Eclipse Memory Analyzer Tool =>当我运行以下OQL查询时,我得到一个奇怪的结果: SELECT c FROM INSTANCEOF my.package.CustomClassloader c 只有一个结果,表明只有一个实例显然正确。
  4. 我还检查了这个link并在创建新的CustomClassloader时实现了一些资源释放,但没有任何变化:PermGen内存仍在增加。

    所以我可能错过了一些东西,点(1-2)和(3)之间的区别显示了我不理解的东西。我在哪里可以了解什么是错的? 由于我所遵循的所有教程都显示了如何使用“搜索最近的GC根”功能搜索泄漏的引用(在我的情况下没有),我不知道如何跟踪错误。

    编辑1:我上传了堆转储here的示例。可以使用以下查询在visualvm中选择未卸载的ClassLoader:select s from saierp.core.framework.system.SAITaskClassLoader s 可以看到有4个实例,并且应该已经收集了第3个实例,因为没有GC根...必须有某个引用,但我不知道如何搜索它。任何提示都受到欢迎:)

    编辑2:经过一些更深入的测试后,我看到一种非常奇怪的模式。泄漏似乎取决于OpenJPA正在加载的数据:如果没有加载新数据,那么类加载器可以是GCed,否则不是。下面是我创建一个新的SAITaskClassLoader以“清除”旧代码时使用的代码:

    PCRegistry.deRegister(cl);
    LogFactory.release(cl);
    ResourceBundle.clearCache(cl);
    Introspector.flushCaches();
    

    =模式1(类加载器已GCed):=

    1. 新的SAITaskClassLoader
    2. 加载数据D1,D2,...,Dn
    3. 新的SAITaskClassLoader
    4. 加载数据D1,D2,...,Dn
    5. ...
    6. cl-gc

      =模式2(类加载器未GCed):=

      1. 新的SAITaskClassLoader
      2. 加载数据D1,D2,D3
      3. 新的SAITaskClassLoader
      4. 加载数据D3,D4,D5
      5. 新的SAITaskClassLoader
      6. 加载数据D5,D6,D7
      7. ...
      8. cl-nogc

        在所有情况下,已清除的SAITaskClassLoader都没有GC根。我们正在使用OpenJPA 1.2.1。

        谢谢&最好的问候

3 个答案:

答案 0 :(得分:6)

如果没有CustomClassLoader源代码的片段或实际的堆转储,则很难找到问题所在。您的CustomClassLoader不能是单身人士。如果是,你的设计无法工作(或者我错过了什么)。

您需要获取ClassLoader类型CustomClassLoader实例的列表,并跟踪对这些对象的引用。

这些帖子可能会帮助您进一步分析它,并深入了解ClassLoader漏洞的暗示:

答案 1 :(得分:2)

类加载器的垃圾收集是一项非常棘手的业务。使用JProfiler,我看到以下对当前活动的自定义类加载器的传入引用链:

enter image description here

这表明你有一个静态字段" singleInstance"在您的自定义类加载器中引用类加载器本身。您应该尝试在重新部署时清除该字段,以便VM更容易收集类加载器。

关于Eclipse MAT获得的结果的注释:它删除了所有不可访问的对象。 JProfiler默认也这样做。因此,之前的三个类加载器应该是垃圾收集的,但它们不是,因为JVM对于类加载器GC具有特殊规则,而这些规则未被堆中的标准引用捕获。

免责声明:我公司开发JProfiler

答案 2 :(得分:1)

最后,我可以关闭这个bug,因为它似乎与OpenJPA和非参数化查询相关联。要查看的另一个主题:Custom ClassLoader not garbage collected