【问题标题】:java heap and thread analysis for memory leak内存泄漏的java堆和线程分析
【发布时间】: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 的 活动对象,则不太可能存在内存泄漏

标签: java multithreading garbage-collection weblogic12c


【解决方案1】:

根据您的堆转储,您最大的内存问题是 int 数组,实际上它占用了将近 70 % 的堆(是的,而是对大小列进行排序)。

  1. 在堆转储中选择它,右键单击并选择Show in Instances View
  2. 然后浏览最大的对象并为每个对象右键单击并选择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 服务器所以:

  1. 启动http://localhost:7000/
  2. 然后点击Show instance counts for all classes (including platform)

然后您将看到按创建的新实例总数排序的类列表。然后,您可以使用 VisualVM 执行我在答案的第一部分中描述的操作,以找到内存泄漏的根本原因。

你也可以使用jhat

  1. 通过选择顶级课程,然后为每个课程选择
  2. 单击一个“对此对象的引用”
  3. 然后点击Exclude weak refs

然后您将看到每个实例的 GC 根,如下图所示:

另一种方法是使用Eclipse Memory Analyzer,也称为MAT

  1. 用它打开第二个快照
  2. 选择视图histogram
  3. 然后右键单击每个顶级类
  4. 选择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[] 的实例?基于此stackoverflow.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 footprintenable GC logs 并解析您有多少 FullGC 暂停也很棒:

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

    在理想世界中它应该是 0 :)

    顺便说一句,整个article 可能对你有帮助。

    【讨论】:

    • 如何区分内存泄漏和大内存占用?以及消耗大量内存的代码?
    • @optimus,内存泄漏 - 你没有清理它。这意味着这些对象仍然可以访问 - 因此,您只需要找到一个根即可。占用空间大 - 您有很多相同的对象,但它们已经无法从根目录访问。你需要在可达范围内找到相同的对象 - 找到一个根。
    • @optimus,当你在你的kit中找到root时,我需要点击“QuickInfo”来查看stacktrace,这个对象是被创建的。
    • 感谢您对 yourkit 的推荐。我目前正在使用 MAT。
    • @optimus 这意味着您的内存没有大问题。或者至少是泄漏。
    猜你喜欢
    • 2016-12-09
    • 2012-02-17
    • 1970-01-01
    • 1970-01-01
    • 2013-05-24
    • 1970-01-01
    • 2015-05-20
    • 2018-07-15
    • 2016-01-26
    相关资源
    最近更新 更多