我们有一个在Glassfish V2.1.1下运行的复杂应用程序。为了能够动态地加载我们的代码,我们实现了一个CustomClassloader,它能够重新定义类。
行为非常简单:当动态加载的类发生更改时,将“删除” CustomClassloader的当前实例,并创建一个新实例来重新定义所需的类。
这很好用,除了在几次重载相同的类之后(因此每次创建新的CustomClassloader时),我们都会收到PermGen空间错误,因为CustomClassloader的其他实例没有被垃圾回收。 (该类应该只有一个实例)
我尝试了不同的方法来跟踪泄漏的位置:
SELECT c FROM INSTANCEOF my.package.CustomClassloader c
只有一个结果,表明只有一个实例显然是不正确的。 我还检查了此link并在创建新的CustomClassloader时实现了一些资源释放,但没有任何变化:PermGen内存仍在增加。
因此,我可能丢失了一些东西,而(1-2)点和(3)点之间的差异表明了我不理解的东西。我在哪里可以找到问题所在?
由于我所遵循的所有教程都显示了如何使用“搜索最近的GC根目录”功能来搜索泄漏的引用(在我的情况下没有),所以我不知道如何跟踪错误。
编辑1:我上传了一个堆转储here的示例。可以使用以下查询在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(Classloader已进行GCed):=
=模式2(未对Classloader进行GC):=
在所有情况下,已清除的SAITaskClassLoader都没有GC根目录。我们正在使用OpenJPA 1.2.1。
谢谢与最诚挚的问候
最佳答案
如果没有CustomClassLoader
的源代码片段或实际的堆转储,将很难找到问题所在。您的CustomClassLoader
不能是单例。如果是这样,您的设计将无法正常工作(或者我错过了一些东西)。
您需要获取ClassLoader
类型的CustomClassLoader
实例的列表,并跟踪对这些对象的引用。
这些帖子可以帮助您进一步分析它,并深入了解ClassLoader泄漏的黑暗细节: