7

我们有一个在 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 只有一个结果,表明只有一个实例,这显然是正确的。

我还检查了此链接并在创建新的 CustomClassloader 时实现了一些资源释放,但没有任何变化:PermGen 内存仍在增加。

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

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

编辑 2:经过一些更深入的测试后,我看到了一个非常奇怪的模式。泄漏似乎取决于 OpenJPA 正在加载的数据:如果没有加载新数据,则可以对类加载器进行 GC,否则不会。这是我在创建新的 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. ...

cl-gc

= 模式 2(类加载器未经过 GC):=

  1. 新的 SAITaskClassLoader
  2. 加载数据 D1、D2、D3
  3. 新的 SAITaskClassLoader
  4. 加载数据 D3、D4、D5
  5. 新的 SAITaskClassLoader
  6. 加载数据 D5、D6、D7
  7. ...

cl-nogc

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

谢谢和最好的问候

4

3 回答 3

6

如果没有源代码片段CustomClassLoader或实际堆转储,将很难追查问题。你CustomClassLoader不能是单身人士。如果是这样,你的设计就不能工作(或者我错过了一些东西)。

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

这些帖子可能会帮助您进一步分析它并深入了解寻找 ClassLoader 泄漏的黑暗细节:

于 2012-12-08T15:55:35.357 回答
2

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

在此处输入图像描述

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

关于您使用 Eclipse MAT 获得的结果的注释:它删除了所有不可强可达的对象。JProfiler 默认也这样做。所以前面的三个类加载器应该被垃圾回收,但它们不是,因为 JVM 对类加载器 GC 的特殊规则没有被堆中的标准引用捕获。

免责声明:我公司开发JProfiler

于 2012-12-10T11:33:56.027 回答
1

最后,我可以关闭这个 bug,因为它似乎与 OpenJPA 和非参数化查询相关联。另一个要查看的线程:自定义类加载器未收集垃圾

于 2014-12-03T23:21:56.297 回答