内存泄漏的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 中名为 leakHashMap 中,所以我知道我的内存问题可能是由于这个特殊的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不当导致的内存泄漏案例&源码分析

Valgrind 上的内存堆和泄漏总结

javaThreadLocal 内存泄漏 代码演示 实例演示

分析 ThreadLocal 内存泄漏问题