内存泄漏的java堆和线程分析
Posted
技术标签:
【中文标题】内存泄漏的java堆和线程分析【英文标题】:java heap and thread analysis for memory leak 【发布时间】:2016-10-02 06:58:28 【问题描述】:我的 WebLogic 服务器配置了 16gb 的堆空间,但在大多数用户开始工作时,在生产使用的 1 小时内使用了 90%。我观察到,每当发生这种情况时,都会有几个线程被卡住。
当堆大约有 10% 空闲时,我捕获了堆转储。如何检查堆转储以找出导致此问题的内存泄漏或进程代码。
我试图了解内存泄漏,运行 JMap 和 Eclipse MAT 等工具,但可能由于缺乏经验,我无法理解这些工具试图显示什么。或者我应该如何/注意什么?
我要分析 GC 堆转储之前/之后。
我查看了线程转储,没有“等待锁定”对象线程,线程类似如下所示,没有明显原因的线程卡住。
【问题讨论】:
您应该进行几次线程转储,以查看 ExecuteThread '0' 的确切操作以及它是否在 JSP (goto.jsp) 上被阻塞。这可能是内存泄漏的根本原因。不要考虑被阻塞的 ExecuteThread '3',因为它是一个套接字复用器线程。 您是否检查了 ChangeAwareClassLoader 的每个实例的已加载类列表? 我看不到 227MB = 90% 的 16GB 堆? @tair 我做了一个 jmap live heap dump,是这个原因吗? @optimus 如果堆转储能够将 16GB 堆整理到 227MB 的 活动对象,则不太可能存在内存泄漏 【参考方案1】:根据您的堆转储,您最大的内存问题是 int 数组,实际上它占用了将近 70 % 的堆(是的,而是对大小列进行排序)。
-
在堆转储中选择它,右键单击并选择
Show in Instances View
然后浏览最大的对象并为每个对象右键单击并选择Show Nearest GC Root
以查看哪个对象仍然具有对 int 数组的硬引用,从而无法进行 GC。
假设它是内存泄漏,它可以帮助您找到内存泄漏。
请参阅下面的 Nearest GC Root
示例,它允许识别我有意添加到程序中的泄漏,只是为了展示这个想法。正如您在屏幕截图中看到的那样,我有一个 int 数组,它不符合 GC 条件,因为它存储在我的班级 leak
中名为 leak
的 HashMap
中,所以我知道我的内存问题可能是由于这个特殊的HashMap
,特别是如果我有许多其他导致这个HashMap
的对象。
注意:当您尝试识别泄漏时请耐心等待,因为它并不总是很明显,理想的情况是您有一个占用整个堆的巨大对象,但显然不是您的情况没有什么很明显的,这就是我建议首先研究 int 数组的原因。不要忘记它也可以是小的 int 数组,但有数千个数组具有相同的 Nearest GC Root
。
另一个技巧,如果您有JProfiler,您只需关注this wonderful tutorial 即可找到您的泄漏点。
回复更新:
更好地确定内存泄漏根本原因的一种简单方法是至少获取 2 个堆转储,然后使用类似 jhat 的工具将它们与语法进行比较
jhat -J-Xmx2G -baseline $path-to-the-first-heap-dump $path-to-the-second-heap-dump
它将在端口 7000
上启动一个小型 HTTP 服务器所以:
-
启动http://localhost:7000/
然后点击
Show instance counts for all classes (including platform)
然后您将看到按创建的新实例总数排序的类列表。然后,您可以使用 VisualVM 执行我在答案的第一部分中描述的操作,以找到内存泄漏的根本原因。
你也可以使用jhat
-
通过选择***课程,然后为每个课程选择
单击一个“对此对象的引用”
然后点击
Exclude weak refs
然后您将看到每个实例的 GC 根,如下图所示:
另一种方法是使用Eclipse Memory Analyzer
,也称为MAT
。
-
用它打开第二个快照
选择视图
histogram
然后右键单击每个***类
选择Merge Shortest Paths To GC Roots
/Exclude All references
然后您将看到类似于下一个屏幕截图的内容:
【讨论】:
我已经上传了我的 int imgur.com/IAKt5Zm 实例视图,没有参考 继续使用其他int数组,选择它们中的每一个,然后右键显示最近的gc root尝试找到几个具有相同gc root的int数组 @Nicholas Filotto 几乎每个 int 数组都有不同的 GC 根,其中大部分是空的。 我打开堆转储,发现 char[] 是最大的支配者,但对于 visualjvm 是 int[] 我查看了使用线程实现的代码。会不会是导致 ChangeAwareClassLoader 存储了这么多 int[] 的实例?基于此***.com/questions/6470651/…【参考方案2】:JDK 的“jmap -histo”命令会将所有类的对象计数/字节转储到文本文件中。如果您随着时间的推移捕获/比较其中一些转储,您将看到哪些转储不断增长——您的内存泄漏。 -histo 的开销远低于捕获完整堆转储的开销。
比较几个转储(如 python 脚本detailed here)似乎太小了一个样本,所以我写了一个开源工具(here)在后台运行这个 jmap -histo 命令(在一个区间)。它具有实时显示并跟踪每个类的字节数上升的时间百分比。
【讨论】:
【参考方案3】:看来您可能遇到了内存泄漏的情况。您最好的方法是使用 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
设置后,请按照说明here 检测泄漏。
希望对你有帮助!!
【讨论】:
我的JDK好像理解有问题 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder 没错。我的错 !!这些参数是旧 JRockit JVM 遗留下来的。您可以将其从启动参数中删除。没问题。 我使用的是 JDK 6,这就是我无法启用 JFR 的原因。我已经升级到 JDK 7。 我已启用 JFR,如何发现异常? 您最好的做法是按照here 中描述的说明进行操作。基本上,您应该启动您的应用程序服务器并让 JFR 记录,直到它抛出 OutOfMemory 错误。使用此记录,您应该查看分配选项卡并过滤您的包结构,以检查正在分配的对象及其样本数量和占用的空间。这应该可以让您知道是什么物体在突破它的边界。【参考方案4】:我是名为Plumbr 的工具的开发人员之一。在内存使用过多的情况下,我们会自动分析堆内容。您可能会发现它很有用。
【讨论】:
我想改用“免费”工具。 完全可以理解 :) 但如果这是一次性问题 - 请免费试用 14 天,并在不附加任何条件的情况下解决您的问题。 感谢您的提议,但我可能需要学习技能,而不是依赖工具来查找内存泄漏。【参考方案5】:根据您的 cmets:您有 16GB 堆的 Java 7,没有明确指定 GC 算法,因此 Java 7 的默认值是吞吐量 GC,这不适合大多数 Web 应用程序,因为它会导致大堆的长时间 GC 暂停。
切换到 ConcurrentMarkSweep GC,这样 GC 不会等到你的内存填满,而是会尽最大努力逐步收集垃圾,这样你就会有更少的 Stop The World 暂停。
【讨论】:
JDK 6 中是否提供 ConcurrentMarkSweep GC? @optimus 是的,我记得我什至在 JDK 5 上也使用过它 酷。我会仔细阅读并建议我的产品经理使用 CMS GC 而不是默认的并行 GC【参考方案6】:你试过yourkit profiler吗?它不是免费的,但您可以评估 30 天。在这种情况下,如果您转储包含所有对象(不仅是实时对象),您也可以检查它们的根。因为可能是你没有内存泄漏,但是太大了memory footprint。 enable GC logs 并解析您有多少 FullGC 暂停也很棒:
grep "Full GC" jvm_gc.log | wc -l
在理想世界中它应该是 0 :)
顺便说一句,整个article 可能对你有帮助。
【讨论】:
如何区分内存泄漏和大内存占用?以及消耗大量内存的代码? @optimus,内存泄漏 - 你没有清理它。这意味着这些对象仍然可以访问 - 因此,您只需要找到一个根即可。占用空间大 - 您有很多相同的对象,但它们已经无法从根目录访问。你需要在可达范围内找到相同的对象 - 找到一个根。 @optimus,当你在你的kit中找到root时,我需要点击“QuickInfo”来查看stacktrace,这个对象是被创建的。 感谢您对 yourkit 的推荐。我目前正在使用 MAT。 @optimus 这意味着您的内存没有大问题。或者至少是泄漏。以上是关于内存泄漏的java堆和线程分析的主要内容,如果未能解决你的问题,请参考以下文章
Java内存泄漏分析系列之一:使用jstack定位线程堆栈信息
Java Review - 线程池中使用ThreadLocal不当导致的内存泄漏案例&源码分析
Java Review - 线程池中使用ThreadLocal不当导致的内存泄漏案例&源码分析