我有一个内存转储,这是我从一个垂死的应用程序中做的。它已经消耗了所有可用的堆(-Xmx1024m)。它使用com.gargoylesoftware.htmlunit.WebClient
来抓取网页。每分钟发出几个http请求,几天后就死了。正如我从转储中看到的,它有大约1750个HtmlPage
类实例,每个实例都有相关对象的色调,包括抓取页面的完整内容。
我不明白为什么HtmlPage
不被垃圾收集。我调查了实例引用,我没有看到任何代码持有对它的引用,VisualVM说"没有找到GC根"。根据我的理解,它应该意味着对象符合gc的条件,但它不起作用。
应用程序作为一个简单的独立进程运行,它不使用任何web容器或应用服务器。
提示吗?我还应该调查什么?
规格:
- htmlunit v2.7
- java版本"1.6.0_13"Java(TM) SE运行环境(build 1.6.0_13-b03)Java HotSpot(TM) Server VM (build 11.3-b02, mixed mode)
- Linux我。局域网2.6.18-128。el5 #1 SMP星期三十二月17 11:42:39 EST 2008 i686 i686 i386 GNU/Linux
Update1
我已经尝试通过YourKit Java Profiler分析转储。它显示了许多保留大小为310mb的java.lang.ref.Finalizer
对象。它们是为net.sourceforge.htmlunit.corejs.javascript.NativeGenerator#finalize()
终结器创建的,NativeGenerator
指向Window
,然后指向HtmlPage
和所有内容。
有谁知道他们为什么留在记忆里吗?
注意:很奇怪,但是VisualVM显示"pending finalization"为零
当一个对象具有重要的finalize()方法时,当创建该对象的实例时,JVM创建java.lang.ref.Finalizer,它保存对所创建对象的引用,因此在finalize()方法完成之前它不会被垃圾收集。内存泄漏来自于那些java.lang.ref。最终定稿没有按时完成。这些终结器的清除是由具有较低优先级的单独的终结器守护线程完成的,因此,如果您使用已实现的finalize()方法创建了许多对象的实例,最终会耗尽内存。
这一切都描述得很好:
http://www.fasterj.com/articles/finalizer2.shtml这是他们建议的解决方案:
"一个明显的方法是增加"Finalizer"守护线程的优先级——没有API,所以你必须遍历所有线程来通过名称找到它,然后增加它的优先级。"
好运确保在完成页面后调用webClient.closeAllWindows() -否则JavaScript线程将继续运行,持有对页面资源的引用等