如何查找 Java 内存泄漏

Posted

技术标签:

【中文标题】如何查找 Java 内存泄漏【英文标题】:How to find a Java Memory Leak 【发布时间】:2010-09-07 14:16:55 【问题描述】:

如何在 Java 中找到内存泄漏(例如,使用 JHat)?我试图在 JHat 中加载堆转储以进行基本查看。但是,我不明白我应该如何找到根引用 (ref) 或其他任何名称。基本上,我可以看出有几百兆字节的哈希表条目([java.util.HashMap$Entry 或类似的东西),但地图到处都在使用......有什么方法可以搜索大地图,或者也许找到大对象树的一般根?

[编辑] 好的,到目前为止我已经阅读了答案,但我们只是说我是一个便宜的混蛋(这意味着我对学习如何使用 JHat 比为 JProfiler 付费更感兴趣)。此外,JHat 始终可用,因为它是 JDK 的一部分。当然,除非 JHat 没有办法,只有蛮力,但我不敢相信会是这样。

另外,我认为我无法真正修改(添加所有地图大小的日志记录)并运行足够长的时间让我注意到泄漏。

【问题讨论】:

这是对 JProfiler 的另一个“投票”。它非常适合堆分析,具有不错的 UI,并且运行良好。正如 McKenzieG1 所说,500 美元比您寻找这些泄漏源的时间要便宜。就工具的价格而言,还不错。 Oracle在这里有一个相关页面:docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/… 【参考方案1】:

我最近处理了我们的应用程序中的内存泄漏。在这里分享我的经验

垃圾收集器定期删除未引用的对象,但它从不收集仍被引用的对象。这就是可能发生内存泄漏的地方。

这里有一些选项可以找出引用的对象。

    使用位于JDK/bin 文件夹中的jvisualvm

选项:观察堆空间

如果你看到堆空间不断增加,那肯定是内存泄漏了。

要查找原因,可以使用sampler下的memory sampler

    在应用程序的不同时间跨度使用jmap(在JDK/bin文件夹中也有)获取Java堆直方图

    jmap -histo <pid> > histo1.txt
    

这里可以分析对象引用。如果某些对象从未被垃圾回收,那就是潜在的内存泄漏。

您可以在本文中阅读一些最常见的内存泄漏原因:Understanding Memory Leaks in Java

【讨论】:

【参考方案2】:

由于我们大多数人已经使用 Eclipse 来编写代码,为什么不使用 Eclipse 中的 Memory Analyzer Tool(MAT)。效果很好。

Eclipse MAT 是一组用于 Eclipse IDE 的插件,它提供了分析 Java 应用程序中的heap dumps 并识别应用程序中的memory problems 的工具。

这有助于开发者通过以下功能发现内存泄漏

    获取内存快照(堆转储) 直方图 保留堆 支配树 探索 GC 根路径 检查员 常见的内存反模式 对象查询语言

【讨论】:

【参考方案3】:

查看screen cast,了解使用 JProfiler 查找内存泄漏。 这是@Dima Malenko 答案的视觉解释。

注意:虽然 JProfiler 不是免费软件,但试用版可以应对当前情况。

【讨论】:

【参考方案4】:

你可以在多次调用垃圾收集器后通过测量内存使用大小来发现:

Runtime runtime = Runtime.getRuntime();

while(true) 
    ...
    if(System.currentTimeMillis() % 4000 == 0)
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    


如果输出数字相等,则说明您的应用程序中没有内存泄漏,但如果您看到内存使用量之间的差异(增加的数字),则说明您的项目中存在内存泄漏。例如:

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

注意,有时通过流和套接字等操作释放内存需要一些时间。您不应该以第一次输出来判断,您应该在特定的时间内对其进行测试。

【讨论】:

【参考方案5】:

大多数时候,在企业应用程序中,给定的 Java 堆大于最大 12 到 16 GB 的理想大小。我发现很难让 NetBeans 分析器直接在这些大型 Java 应用程序上工作。

但通常不需要这样做。您可以使用 jdk 附带的 jmap 实用程序进行“实时”堆转储,即 jmap 将在运行 GC 后转储堆。对应用程序执行一些操作,等到操作完成,然后再进行另一个“实时”堆转储。使用 Eclipse MAT 之类的工具来加载堆转储,对直方图进行排序,查看哪些对象增加了,或者哪些对象最高,这将提供线索。

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

这种方法只有一个问题;巨大的堆转储,即使使用实时选项,也可能太大而无法转移到开发圈,并且可能需要一台具有足够内存/RAM 的机器才能打开。

这就是类直方图出现的地方。您可以使用 jmap 工具转储实时类直方图。这将只给出内存使用的类直方图。基本上它没有链接引用的信息。例如,它可以将 char 数组放在顶部。和下面某处的字符串类。您必须自己绘制连接。

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

不是采用两个堆转储,而是采用两个类直方图,如上所述;然后比较类直方图并查看正在增加的类。看看您是否可以将 Java 类与您的应用程序类相关联。这将给出一个很好的提示。这是一个 pythons 脚本,可以帮助您比较两个 jmap 直方图转储。 histogramparser.py

最后,像 JConolse 和 VisualVm 这样的工具对于查看内存随时间的增长以及是否存在内存泄漏至关重要。最后,有时您的问题可能不是内存泄漏,而是内存使用率高。为此启用 GC 日志记录;使用更高级和新的压缩 GC,如 G1GC;并且可以使用 jstat 等 jdk 工具实时查看 GC 行为

jstat -gccause pid <optional time interval>

其他参考 google 的 -jhat, jmap, Full GC, Humongous allocation, G1GC

【讨论】:

在此处添加了包含更多详细信息的博文 - alexpunnen.blogspot.in/2015/06/…【参考方案6】:

这里的提问者,我不得不说,获得一个不需要 5 分钟就能回答任何点击的工具可以更容易地发现潜在的内存泄漏。

由于人们建议了几种工具(我只尝试了 visual wm,因为我在 JDK 和 JProbe 试用版中得到了它)我虽然应该建议一个构建在 Eclipse 平台上的免费/开源工具,内存分析器(有时被称为SAP 内存分析器)在http://www.eclipse.org/mat/ 上可用。

这个工具真正酷的地方在于,当我第一次打开它时,它会为堆转储建立索引,这使得它可以显示保留堆等数据,而无需为每个对象等待 5 分钟(几乎所有操作都比其他工具快很多)我试过了)。

当您打开转储时,第一个屏幕会显示一个饼图,其中包含最大的对象(计算保留堆),您可以快速向下导航到大到舒适的对象。它还有一个查找可能的泄漏嫌疑人,我认为它可以派上用场,但由于导航对我来说已经足够了,我并没有真正进入它。

【讨论】:

值得注意:显然在 Java 5 及更高版本中,HeapDumpOnCtrlBreak VM 参数不可用。我找到的解决方案(到目前为止,仍在寻找)是使用 JMap 转储 .hprof 文件,然后我将其放入 Eclipse 并使用 MAT 进行检查。 关于获取堆转储,大多数分析器(包括 JVisualVM)都包含将堆和线程转储到文件的选项。【参考方案7】:

我使用以下方法来查找 Java 中的内存泄漏。我使用 jProfiler 取得了巨大成功,但我相信任何具有绘图功能的专用工具(差异更容易以图形形式分析)都可以工作。

    启动应用程序并等待它进入“稳定”状态,此时所有初始化完成且应用程序处于空闲状态。 多次运行怀疑会产生内存泄漏的操作,以允许进行任何与缓存、数据库相关的初始化。 运行 GC 并拍摄内存快照。 再次运行该操作。根据操作的复杂性和所处理数据的大小,操作可能需要运行数次到多次。 运行 GC 并拍摄内存快照。 运行 2 个快照的差异并进行分析。

基本上,分析应该从对象类型的最大正差异开始,并找出导致这些额外对象滞留在内存中的原因。

对于在多个线程中处理请求的 Web 应用程序,分析变得更加复杂,但通用方法仍然适用。

我做了很多专门针对减少应用程序的内存占用的项目,这种带有一些特定于应用程序的调整和技巧的通用方法总是很有效。

【讨论】:

大多数(如果不是全部)Java 分析器为您提供了通过单击按钮调用 GC 的选项。或者您可以从代码中的适当位置调用 System.gc()。 即使我们调用 System.gc() JVM 也可能选择忽略调用。 AFAIK 这是 JVM 特定的。为答案 +1。 究竟什么是“内存快照”?有什么东西可以告诉我我的代码正在运行的每种对象类型的数量吗? 如何从“从对象类型的最大正差异开始”到“找出导致这些额外对象滞留在内存中的原因”?我看到很一般的东西,比如 int[]、Object[]、String 等。我如何找到它们的来源?【参考方案8】:

一个工具是一个很大的帮助。

但是,有时您无法使用工具:堆转储太大以致工具崩溃,您正在尝试对某些生产环境中的机器进行故障排除,而您只能通过 shell 访问,等等。

在这种情况下,了解 hprof 转储文件的方式会有所帮助。

寻找 SITES BEGIN。这向您显示了哪些对象使用的内存最多。但是这些对象并不仅仅按类型集中在一起:每个条目还包括一个“跟踪”ID。然后,您可以搜索该“TRACE nnnn”以查看分配对象的堆栈的顶部几帧。通常,一旦我看到对象的分配位置,我就会发现一个错误,然后就完成了。另外,请注意,您可以使用 -Xrunhprof 选项控制堆栈中记录的帧数。

如果您查看分配站点,并且没有发现任何错误,则必须开始从其中一些活动对象到根对象的反向链接,以找到意外的引用链。这是一个工具真正有帮助的地方,但你可以手动做同样的事情(嗯,用 grep)。不只有一个根对象(即不受垃圾回收影响的对象)。线程、类和堆栈帧充当根对象,它们强烈引用的任何东西都是不可收集的。

要进行链接,请在 HEAP DUMP 部分中查找具有错误跟踪 ID 的条目。这将带您进入 OBJ 或 ARR 条目,其中以十六进制显示唯一的对象标识符。搜索该 id 的所有出现,以找出谁对该对象有强引用。沿着每条路径向后分支,直到找出泄漏的位置。看看为什么一个工具如此方便?

静态成员是内存泄漏的重犯。事实上,即使没有工具,花几分钟时间查看静态 Map 成员的代码也是值得的。地图可以变大吗?有什么东西会清理它的条目吗?

【讨论】:

“堆转储太大,导致工具崩溃”——最后我检查了一下,jhatMAT 显然试图将整个堆转储加载到内存中,因此通常会因@而崩溃987654323@ 大转储(即,来自最需要堆分析的应用程序!)。 NetBeans Profiler 似乎使用不同的算法来索引引用,这可能会在大转储时变得缓慢,但至少不会消耗工具中的无限内存并导致崩溃。【参考方案9】:

NetBeans 有一个内置的分析器。

【讨论】:

【参考方案10】:

有一些工具可以帮助您找到漏洞,例如 JProbe、YourKit、AD4J 或 JRockit Mission Control。最后一个是我个人最了解的。任何好的工具都应该让您深入到可以轻松识别泄漏对象以及分配泄漏对象的位置的级别。

使用 HashTables、Hashmaps 或类似的方法是在 Java 中完全可以泄漏内存的少数几种方法之一。如果我必须手动查找泄漏,我会定期打印我的 HashMap 的大小,然后从那里找到我添加项目并忘记删除它们的那个。

【讨论】:

【参考方案11】:

您确实需要使用跟踪分配的内存分析器。看看JProfiler——他们的“heap walker”功能很棒,并且与所有主要的 Java IDE 集成。它不是免费的,但也不是那么贵(单个许可证 499 美元)——您将很快花费 500 美元的时间来努力使用不太复杂的工具来查找漏洞。

【讨论】:

【参考方案12】:

嗯,总是有一种低技术的解决方案,即在您修改地图时添加记录地图大小,然后搜索地图增长超出合理大小的日志。

【讨论】:

以上是关于如何查找 Java 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 visualvm 查找内存泄漏

Android内存泄漏查找和解决

Android 内存泄漏

如何查找我是不是有内存泄漏

如何排查Java内存泄露

在大型 Java 堆转储中查找内存泄漏的方法