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

这很好用,除了在几次重载相同的类之后(因此每次创建新的CustomClassloader时),我们都会收到PermGen空间错误,因为CustomClassloader的其他实例没有被垃圾回收。 (该类应该只有一个实例)

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

  • visualvm =>我进行堆转储并提取CustomClassloader的所有实例。我可以看到它们都没有完成。当我检查最近的GC根时,visualvm告诉我没有(除了最后一个实例,因为它是使用过的“真实”对象)。
  • jmap/jhat =>它给我几乎相同的结果:我看到了CustomClassloader的所有实例,然后当我单击链接以查看其中一个的引用在哪里时,我得到一个空白页,这意味着没有...
  • Eclipse Memory Analyzer Tool =>运行以下OQL查询时,我得到一个奇怪的结果: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):=
  • 新的SAITaskClassLoader
  • 加载数据D1,D2,...,Dn
  • 新的SAITaskClassLoader
  • 加载数据D1,D2,...,Dn
  • ...


  • =模式2(未对Classloader进行GC):=
  • 新的SAITaskClassLoader
  • 加载数据D1,D2,D3
  • 新的SAITaskClassLoader
  • 加载数据D3,D4,D5
  • 新的SAITaskClassLoader
  • 加载数据D5,D6,D7
  • ...


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

    谢谢与最诚挚的问候

    最佳答案

    如果没有CustomClassLoader的源代码片段或实际的堆转储,将很难找到问题所在。您的CustomClassLoader不能是单例。如果是这样,您的设计将无法正常工作(或者我错过了一些东西)。

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

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

  • Debugging Dynamic ClassLoaders in Heap Dumps
  • How to analyze leaky web-apps
  • A couple of typical causes of ClassLoader leaks
  • 10-08 13:21