题
如何发现 Java 中的内存泄漏(例如使用 JHat)?我尝试在 JHat 中加载堆转储来进行基本查看。但是,我不明白我应该如何找到根引用(参考)或无论它被称为什么。基本上,我可以看出有几百兆字节的哈希表条目([java.util.HashMap$Entry 或类似的东西),但是到处都使用映射......有没有某种方法可以搜索大型地图,或者找到大型对象树的一般根?
编辑]好的,到目前为止,我已经阅读了答案,但是我们只想说我是一个便宜的混蛋(这意味着我对学习如何使用JHAT更感兴趣,而不是支付Jprofiler)。此外,JHat 始终可用,因为它是 JDK 的一部分。当然,除非 JHat 除了暴力之外别无他法,但我不敢相信会出现这种情况。
另外,我认为我无法实际修改(添加日志记录 全部 地图大小)并运行足够长的时间让我注意到泄漏。
解决方案
我使用以下方法来查找 Java 中的内存泄漏。我使用 jProfiler 取得了巨大成功,但我相信任何具有图形功能的专用工具(差异更容易以图形形式分析)都可以工作。
- 启动应用程序并等待其进入“稳定”状态,此时所有初始化均已完成并且应用程序处于空闲状态。
- 多次运行怀疑产生内存泄漏的操作,以允许进行任何缓存、与数据库相关的初始化。
- 运行 GC 并拍摄内存快照。
- 再次运行该操作。根据操作的复杂性和所处理的数据的大小,操作可能需要运行几次到多次。
- 运行 GC 并拍摄内存快照。
- 对 2 个快照运行差异并进行分析。
基本上,分析应该从最大的正差异开始,例如对象类型,并找出导致这些额外对象保留在内存中的原因。
对于在多个线程中处理请求的 Web 应用程序,分析变得更加复杂,但通用方法仍然适用。
我做了很多专门旨在减少应用程序内存占用的项目,这种通用方法加上一些特定于应用程序的调整和技巧总是效果很好。
其他提示
这里的提问者,我不得不说,获得一个不需要 5 分钟来回答任何点击的工具可以更容易地发现潜在的内存泄漏。
由于人们建议使用几种工具(我只尝试了 Visual WM,因为我在 JDK 和 JProbe 试用版中得到了它),所以我认为我应该建议一个构建在 Eclipse 平台上的免费/开源工具,即内存分析器(有时称为 SAP 内存)分析仪)可用于 http://www.eclipse.org/mat/ .
这个工具真正酷的是,当我第一次打开它时,它对堆转储进行了索引,这使得它可以显示保留堆等数据,而无需为每个对象等待 5 分钟(几乎所有操作都比我尝试过的其他工具快得多) 。
当您打开转储时,第一个屏幕会向您显示一个饼图,其中包含最大的对象(计算保留的堆),并且可以快速向下导航到太大的对象以方便查看。它还有一个“查找可能的泄漏嫌疑人”,我认为它可以派上用场,但由于导航对我来说已经足够了,所以我并没有真正了解它。
工具是一个很大的帮助。
但是,有时您无法使用工具:堆转储太大,导致工具崩溃,您正在尝试对某些生产环境中的机器进行故障排除,而您只能访问 shell 等。
在这种情况下,了解 hprof 转储文件的方法会有所帮助。
寻找“站点开始”。这会向您显示哪些对象使用最多的内存。但这些对象并不仅仅按类型聚集在一起:每个条目还包括一个“跟踪”ID。然后,您可以搜索“TRACE nnnn”以查看分配对象的堆栈的前几帧。通常,一旦我看到对象的分配位置,我就会发现一个错误,然后我就完成了。另请注意,您可以使用 -Xrunhprof 选项控制堆栈中记录的帧数。
如果您检查分配站点,没有发现任何错误,则必须开始从其中一些活动对象到根对象的向后链接,以找到意外的引用链。这是工具真正有用的地方,但您可以手动执行相同的操作(好吧,使用 grep)。不只有一个根对象(即不受垃圾回收影响的对象)。线程、类和堆栈帧充当根对象,它们强引用的任何内容都是不可收集的。
要进行链接,请在 HEAP DUMP 部分中查找具有错误跟踪 ID 的条目。这将带您到 OBJ 或 ARR 条目,其中显示十六进制的唯一对象标识符。搜索该 id 的所有出现位置,以查找谁对该对象具有强引用。沿着每条路径的分支向后追踪,直到找出泄漏位置。明白为什么工具如此方便吗?
静态成员是内存泄漏的重犯。事实上,即使没有工具,花几分钟检查静态 Map 成员的代码也是值得的。地图可以变大吗?有没有什么东西清理过它的条目?
大多数时候,在企业应用程序中,给定的 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 数组放在顶部。下面是 String 类。你必须自己画出联系。
jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt
不像上面描述的那样,采用两个类直方图,而不是采用两个堆转储;然后比较类别直方图并查看正在增加的类别。看看您是否可以将 Java 类与您的应用程序类相关联。这将给出一个很好的提示。这是一个 pythons 脚本,可以帮助您比较两个 jmap 直方图转储。 直方图解析器.py
最后,JConolse 和 VisualVm 等工具对于查看内存随时间的增长情况以及是否存在内存泄漏至关重要。最后,有时你的问题可能不是内存泄漏,而是内存使用率过高。为此启用 GC 日志记录;使用更高级和新的压缩 GC,如 G1GC;您可以使用 jdk 工具(例如 jstat)实时查看 GC 行为
jstat -gccause pid <optional time interval>
其他参考google的-jhat、jmap、Full GC、Humongous Allocation、G1GC
有一些工具可以帮助您找到泄漏,例如 JProbe、YourKit、AD4J 或 JRockit Mission Control。最后一个是我个人最了解的。任何好的工具都应该让您深入到可以轻松识别泄漏的级别以及泄漏对象的分配位置。
使用 HashTables、Hashmaps 或类似的方法是 Java 中真正泄漏内存的少数方法之一。如果我必须手动查找泄漏,我会定期打印 HashMap 的大小,然后从那里找到我添加项目但忘记删除它们的位置。
好吧,总有一种技术含量较低的解决方案,即在修改地图时添加地图大小的日志记录,然后搜索日志以查找地图增长超出合理大小的日志。
NetBeans 有一个内置的分析器。
您确实需要使用跟踪分配的内存分析器。看一眼 JProfiler - 他们的“heap walker”功能很棒,并且与所有主要的 Java IDE 集成。它不是免费的,但也不是那么昂贵(单个许可证 499 美元)——您将很快花费 500 美元的时间来努力使用不太复杂的工具来查找泄漏。