Java堆和线程分析用于内存泄漏。

3
我的WebLogic服务器配置了16GB的堆空间,但在大多数用户开始工作后的1小时内,堆空间就已经使用了90%。每当发生这种情况时,我会发现有几个线程被卡住了。
当堆空间仅剩约10%时,我捕获了堆转储。如何检查堆转储以找出导致此问题的内存泄漏或进程、代码。
我尝试理解内存泄漏,运行JMap和Eclipse MAT等工具,但由于缺乏经验,我无法理解这些工具试图展示的内容。或者我应该注意什么?
我有GC前/后的堆转储可以分析。
我已经审核了线程转储,没有“等待锁定”对象线程,线程与下面显示的相似,线程被卡住而没有明显的原因。

你应该获取多个线程转储以准确查看ExecuteThread '0'正在做什么,以及它是否在JSP(goto.jsp)上被阻塞。这可能是内存泄漏的根本原因。不要考虑ExecuteThread '3',因为它是一个套接字复用器线程而被阻塞。 - Emmanuel Collin
1
我无法理解227MB如何等于16GB堆的90%。 - Tair
@kevin ternet,我已经学习了ChangeAwareClassLoader的课程,但如何判断某些内容是否异常? - ilovetolearn
3
如果堆转储能将16GB的堆缩小到227MB的_live objects_,你几乎不可能有内存泄漏。请注意,本翻译仅代表原文意思,不包含解释或其他额外内容。 - Tair
@Emmanuel Collin,我已经获取了多个线程转储,但无法找到有关锁定的更多详细信息。但是,使用Eclipse MAT确实告诉我,内存消耗似乎与此文件有关。 - ilovetolearn
显示剩余7条评论
6个回答

3
根据您的堆转储,您最大的内存问题是int数组,确实占用了近70%的堆内存(是的,请按大小列排序)。
  1. 在堆转储中选择它,右键单击并选择在实例视图中显示
  2. 然后浏览最大的对象,并为每个对象右键单击并选择显示最近的GC根,以查看哪个对象仍具有对int数组的硬引用,从而防止其有资格进行GC。

假设这是内存泄漏,它可能有助于找到您的内存泄漏。

以下是最近的GC根示例,可帮助识别我故意添加到程序中的泄漏以展示该想法。如您在屏幕截图中所见,我有一个int数组,无法有资格进行GC,因为它存储在名为leakHashMap中,该HashMap位于我的Application类中,因此我知道我的内存问题可能是由于特定的HashMap引起的,尤其是如果我有许多其他对象导致了这个HashMap

enter image description here

NB:当您尝试识别泄漏时,请耐心等待,因为它并不总是显而易见的。理想情况下,您有一个占用整个堆的巨大对象,但显然这不是您的情况,没有什么真正明显的原因,这就是为什么我首先建议调查int数组。不要忘记,它也可能是小的int数组,但有成千上万个具有相同最近的GC根的数组。

另一个技巧,如果您拥有JProfiler,则可以简单地按照此精彩教程来查找泄漏。

响应更新:

更好地识别内存泄漏根本原因的一种简单方法是至少获取2个堆转储,然后使用类似jhat的工具进行比较,语法如下:

jhat -J-Xmx2G -baseline ${path-to-the-first-heap-dump} ${path-to-the-second-heap-dump}

它将在端口7000上启动一个小型HTTP服务器,因此:
  1. 启动http://localhost:7000/
  2. 然后单击Show instance counts for all classes (including platform)
然后您将看到按新实例总数排序的类列表。您可以使用VisualVM执行我在答案的第一部分中描述的操作,以查找您内存泄漏的根本原因。
您也可以使用jhat
  1. 选择顶级类之一,然后针对每个类
  2. 单击一个“Reference to this Object”
  3. 然后单击Exclude weak refs
您将看到每个实例的GC root,如下图所示:

enter image description here

另一种方法是使用称为MATEclipse Memory Analyzer
  1. 使用MAT打开第二个快照
  2. 选择视图histogram
  3. 然后针对每个顶级类,右键单击
  4. 选择Merge Shortest Paths To GC Roots / Exclude All references
您将看到类似于下图的内容:

enter image description here


@Nicholas Filotto,几乎每个int数组都有不同的GC根,其中大部分是空的。 - ilovetolearn
我有一个初始堆大小为16GB,新大小为256MB。现在,我已将新大小增加到2GB,看起来pargen空间使用不到5%。这可能是由于新大小的分配引起的吗? - ilovetolearn
您使用哪个JDK版本?您设置了哪些JVM参数?请提供具体细节。 - Nicolas Filotto
我使用了以下配置:-Xms16g -Xmx16g -XX:NewSize=2g -XX:MaxNewSize=2g -XX:PermSize=512m -XX:MaxPermSize=1g - ilovetolearn
嗨,Nicolas,感谢您在跟踪内存泄漏方面的指导。这对我在故障排除方面有所帮助 =) - ilovetolearn
显示剩余13条评论

2
JDK的"jmap -histo"命令会将所有类的对象数量/字节转储到文本文件中。如果您隔一段时间捕获/比较几个这样的转储,您将看到哪些持续增长--即内存泄漏。与捕获完整堆转储相比,-histo的开销要低得多。

仅比较几个转储(例如此处详细说明的Python脚本)似乎是一个过小的样本,因此我编写了一个开源工具(在此处),该工具在后台运行此jmap -histo命令(间隔一定时间)。它有一个实时显示并跟踪每个类的字节计数上升所占的时间百分比。


1
根据您的评论:您使用的是带有16GB堆的Java 7,没有明确指定GC算法,因此Java 7的默认值为吞吐量GC,对于大多数Web应用程序来说并不适合,因为它会导致长时间的GC暂停。
切换到ConcurrentMarkSweep GC,这样GC将不会等待内存填满,而是尽最大努力按增量方式收集垃圾,这样您就会有更少的Stop The World暂停。

JDK 6 中是否可用 ConcurrentMarkSweep GC? - ilovetolearn
@optimus 是的,我记得甚至在JDK 5上使用它。 - Tair
很棒。 我会研究一下并向我的产品经理建议使用CMS GC而不是默认的Parallel GC。 - ilovetolearn

1
似乎您可能遇到了内存泄漏的情况。您最好使用Java Mission Control和Flight Recorder来获取泄漏的类和方法。
您应该使用以下参数设置您的Weblogic托管服务器:
-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=8999 
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false 
-XX:+UnlockCommercialFeatures 
-XX:+FlightRecorder

当您设置此项时,请按照此处的说明检测泄漏。
希望能帮到您!!

我的JDK似乎无法理解-XX:+ UnlockCommercialFeatures -XX:+ FlightRecorder的问题。 - ilovetolearn
@Iian Salviano,我在生产环境中没有遇到过内存不足的情况,因为我们手动GC堆空间。但是,我正在尝试通过我们的测试环境跟踪内存泄漏。 - ilovetolearn
嗨 @optimus,你能在测试环境中测试它吗? - Ilan Salviano
我无法察觉到任何差异。在测试环境中,所有的活动看起来都很正常。我是否缺乏一些负载以触发大量内存消耗? - ilovetolearn
可能是的。这就是为什么JFR对你来说是一个好选择,因为你可以在生产环境中插入并记录所有运行情况。此操作对CPU的负载大约为3%。 - Ilan Salviano
显示剩余4条评论

1

我是Plumbr工具的开发人员之一。除其他功能外,我们还会在内存使用过多时自动分析堆内容。您可能会发现它很有用。


我想使用“免费”的工具。 - ilovetolearn
1
完全可以理解 :) 但如果这只是一次性的问题 - 可以免费试用我们的14天服务,无需任何附加条件解决您的问题。 - Nikem
2
谢谢你的提议,但我可能需要学习这些技能,而不是依赖工具来查找内存泄漏。 - ilovetolearn
Plumblr已经不存在了。 - roronoa_zoro
@roronoa_zoro 是的,我们被收购了 :) - Nikem

1

你是否尝试过yourkit profiler?它不是免费的,但你可以评估它30天。在这种情况下,如果你的转储包含所有对象(而不仅仅是活动对象),你将能够检查它们的根。因为可能不是内存泄漏,而是内存占用过大memory footprint。此外,启用GC日志并解析你有多少FullGC暂停也会很好:enable GC logs

grep "Full GC" jvm_gc.log | wc -l

在理想情况下应该是0 :)
顺便说一下,整篇文章对您可能会有所帮助。

如何区分内存泄漏和大内存占用?以及哪些代码正在消耗大量内存? - ilovetolearn
@optimus,内存泄漏 - 你没有完全清理它。这意味着这些对象仍然可以被访问 - 所以,你只需要找到一个根源。大的足迹 - 你有很多相同的对象,但它们已经无法从根源访问。你需要在可访问范围内找到相同的对象 - 找到一个根源。 - Jimilian
@optimus,当你在yourkit中找到了根节点时,我需要点击“QuickInfo”来查看创建该对象的堆栈跟踪。 - Jimilian
谢谢您推荐YourKit。目前我正在使用MAT。 - ilovetolearn
我之前使用的是 -Xms16g -Xmx16g -XX:NewSize=256M -XX:MaxNewSize=256M,堆空间使用率经常达到80-90%。但是在切换到 -Xms16g -Xmx16g -XX:NewSize=2g -XX:MaxNewSize=2g 后,堆空间使用率只有30-40%。这是怎么回事? - ilovetolearn
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接