我们有一个在Glassfish V2.1.1下运行的复杂应用程序。为了能够动态加载代码,我们实现了一个CustomClassloader,它能够重新定义类。
行为非常简单:当动态加载的类发生更改时,当前CustomClassloader实例被“丢弃”,并创建一个新的实例来重新定义所需的类。
这很有效,但是在同一类被重新加载了几次之后(因此每次都会创建一个新的CustomClassloader实例),我们会出现PermGen空间错误,因为其他CustomClassloader实例没有被垃圾回收。(应该只有一个此类的实例)
我尝试了不同的方法来追踪泄漏的位置:
所以我可能错过了什么,点(1-2)和(3)之间的差异显示出我不理解的内容。我可以在哪里寻找有关问题的想法? 由于我遵循的所有教程都展示了如何使用“搜索最近的GC根”功能来搜索泄漏引用(在我的情况下没有),我不知道如何跟踪错误。
编辑1:我上传了一个堆转储的示例此处。可以在visualvm中使用以下查询选择未卸载的ClassLoader:
谢谢&最好的祝福
行为非常简单:当动态加载的类发生更改时,当前CustomClassloader实例被“丢弃”,并创建一个新的实例来重新定义所需的类。
这很有效,但是在同一类被重新加载了几次之后(因此每次都会创建一个新的CustomClassloader实例),我们会出现PermGen空间错误,因为其他CustomClassloader实例没有被垃圾回收。(应该只有一个此类的实例)
我尝试了不同的方法来追踪泄漏的位置:
- visualvm => 我创建了一个堆转储并提取了CustomClassloader的所有实例。我发现它们中没有一个被finalize。当我检查最近的GC根时,visualvm告诉我没有(除了最后一个实例,因为它是“真正”使用的实例)。
- jmap/jhat => 它给我几乎相同的结果:我看到CustomClassloader的所有实例,然后当我单击其中一个实例的引用链接时,我得到一个空白页面,表示没有引用...
- Eclipse Memory Analyzer Tool => 当我运行以下OQL查询时,我得到一个奇怪的结果:
SELECT c FROM INSTANCEOF my.package.CustomClassloader c
只有一个结果,表明只有一个单一实例,这显然是不正确的。
所以我可能错过了什么,点(1-2)和(3)之间的差异显示出我不理解的内容。我可以在哪里寻找有关问题的想法? 由于我遵循的所有教程都展示了如何使用“搜索最近的GC根”功能来搜索泄漏引用(在我的情况下没有),我不知道如何跟踪错误。
编辑1:我上传了一个堆转储的示例此处。可以在visualvm中使用以下查询选择未卸载的ClassLoader:
select s from saierp.core.framework.system.SAITaskClassLoader s
可以看到有4个实例,前三个应该已经被收集了,因为没有GC根......必须有某个引用,但我不知道如何搜索它。欢迎任何提示 :)
编辑2:经过一些深入的测试,我发现了一个非常奇怪的模式。泄漏似乎取决于OpenJPA加载的数据:如果没有加载新数据,则类加载器可以被GCed,否则就不行。这是我创建新的SAITaskClassLoader时使用的代码,以“清除”旧的代码:
PCRegistry.deRegister(cl);
LogFactory.release(cl);
ResourceBundle.clearCache(cl);
Introspector.flushCaches();
= 模式 1(类加载器被垃圾回收): =
- 创建新的 SAITaskClassLoader
- 加载数据 D1、D2、...、Dn
- 创建新的 SAITaskClassLoader
- 加载数据 D1、D2、...、Dn
- ...
= 模式2(Classloader不会被GC):=
- 新建SAITaskClassLoader
- 加载数据D1,D2,D3
- 新建SAITaskClassLoader
- 加载数据D3,D4,D5
- 新建SAITaskClassLoader
- 加载数据D5,D6,D7
- ...
谢谢&最好的祝福