148

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

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

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

4

12 回答 12

132

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

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

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

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

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

于 2008-09-02T18:49:35.590 回答
50

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

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

这个工具真正酷的是它在我第一次打开它时索引了堆转储,这允许它显示保留堆之类的数据,而无需为每个对象等待 5 分钟(几乎所有操作都比我尝试过的其他工具快很多) .

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

于 2008-09-11T08:29:56.513 回答
14

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

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

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

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

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

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

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

于 2008-09-02T18:30:42.530 回答
10

大多数时候,在企业应用程序中,给定的 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 直方图转储。直方图解析器.py

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

jstat -gccause pid <optional time interval>

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

于 2015-06-24T06:15:44.530 回答
5

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

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

于 2008-09-02T17:48:30.337 回答
4

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

于 2008-09-02T17:39:50.530 回答
1

NetBeans 有一个内置的分析器。

于 2008-09-02T18:30:34.760 回答
0

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

于 2008-09-02T17:46:46.223 回答
0

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

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

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

于 2019-09-18T12:44:29.357 回答
0

查看有关使用 JProfiler 查找内存泄漏的屏幕截图。这是@Dima Malenko Answer 的视觉解释。

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

于 2019-11-21T04:38:10.227 回答
0

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

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

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

  1. 获取内存快照(堆转储)
  2. 直方图
  3. 保留堆
  4. 支配树
  5. 探索 GC Roots 的路径
  6. 督察
  7. 常见的内存反模式
  8. 对象查询语言

在此处输入图像描述

于 2019-12-13T03:49:33.723 回答
0

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

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

以下是一些查找引用对象的选项。

  1. 使用jvisualvm位于JDK/bin文件夹中的哪个

选项:观察堆空间 在此处输入图像描述

如果您看到堆空间不断增加,则肯定存在内存泄漏。

要找出原因,您可以使用memory samplerunder sampler

在此处输入图像描述

  1. 通过在应用程序的不同时间跨度中使用jmap(也可以在文件夹中获得)获取 Java 堆直方图JDK/bin

    jmap -histo <pid> > histo1.txt
    

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

您可以在本文中阅读一些最常见的内存泄漏原因:了解 Java 中的内存泄漏

于 2021-03-24T16:11:22.790 回答