我有一个内存转储,是我从一个垂死的应用程序中创建的。它已消耗所有可用堆 (-Xmx1024m)。它使用 com.gargoylesoftware.htmlunit.WebClient
来抓取网页。每分钟发出几个 http 请求,几天后就死了。正如我从转储中看到的那样,它有大约 1750 个 HtmlPage
类实例,每个实例都带有相关对象的色调,包括已抓取页面的全部内容。
我不明白为什么 HtmlPage
没有被垃圾回收。我已经调查了实例引用,但我没有看到任何我的代码持有对它的引用,VisualVM 说“找不到 GC 根”。据我了解,这应该意味着该对象符合 gc 的条件,但它不起作用。
该应用程序作为一个简单的独立进程运行,它不使用任何 Web 容器或应用程序服务器。
有什么提示吗?我还应该检查什么?
规范:
- htmlunit v2.7
- java version "1.6.0_13" Java(TM) SE Runtime Environment (build 1.6.0_13-b03) Java HotSpot(TM) Server VM (build 11.3-b02, mixed mode)
- Linux my.lan 2.6.18-128.el5 #1 SMP Wed Dec 17 11:42:39 EST 2008 i686 i686 i386 GNU/Linux
更新1
我试图通过 YourKit Java Profiler 分析转储。它向我展示了很多保留大小为 310mb 的 java.lang.ref.Finalizer
对象。它们是为 net.sourceforge.htmlunit.corejs.javascript.NativeGenerator#finalize()
终结器创建的,而 NativeGenerator
指的是 Window
,然后到 HtmlPage
和所有内容。
有谁知道为什么他们留在内存中?
注意:很好奇,但 VisualVM 将“待完成”显示为零。
最佳答案
当对象具有非平凡的 finalize() 方法时,在创建对象的实例时,JVM 会创建 java.lang.ref.Finalizer,它保存对创建对象的引用,因此它不会在 finalize() 方法之前被垃圾收集完成了。内存泄漏来自那些没有及时清除的 java.lang.ref.Finalizer-s。这些终结器的清除是由一个单独的具有较低优先级的终结器守护线程完成的,因此如果您使用已实现的 finalize() 方法创建大量对象实例,您会及时耗尽内存。
它的所有描述都非常好:
http://www.fasterj.com/articles/finalizer2.shtml
这是他们建议的解决方案:
“一个明显的方法是增加“Finalizer”守护线程的优先级 - 没有用于此的 API,因此您必须遍历所有线程以按名称找到它,然后增加它的优先级。”
祝你好运
关于java - 寻找内存泄漏,VisualVM : "No GC root found". 接下来是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8489995/