如何找到Java内存泄漏


142

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

[编辑]好的,到目前为止,我已经阅读了答案,但是我们只能说我是个贱人(这意味着我对学习如何使用JHat而不是为JProfiler付费更感兴趣)。另外,由于JHat是JDK的一部分,因此始终可用。除非当然不能使用JHat,否则只能使用蛮力,但是我不敢相信这种情况。

另外,我认为我将无法进行实际修改(添加所有地图尺寸的记录)并运行足够长的时间,以至于我无法注意到泄漏。


这是JProfiler的另一个“投票”。它对于堆分析非常有效,具有不错的用户界面,并且效果很好。正如McKenzieG1所说,500美元比寻找这些泄漏源要花费的时间便宜。就工具的价格而言,还不错。
2008年

Answers:


126

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

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

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

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

我做了很多项目,目的是减少应用程序的内存占用,并且这种通用方法以及一些针对应用程序的调整和技巧总是很有效。


7
大多数(如果不是全部)Java Profiler为您提供了单击按钮即可调用GC的选项。或者,您可以在代码中的适当位置调用System.gc()。
Dima Malenko 2010年

3
即使我们调用System.gc(),JVM也会选择忽略该调用。AFAIK这是JVM特定的。为答案+1。
Aniket Thakur 2013年

4
什么是“内存快照”?是否可以告诉我代码正在运行的每个对象类型的数量?
2015年

2
如何从“从按对象类型的最大正差异开始”到“查找是什么导致那些多余的对象滞留在内存中”?我看到非常普通的东西,例如int [],Object [],String等。如何找到它们的来源?
Vituel '17

48

发问者在这里,我不得不说得到一种不需要5分钟即可回答任何点击的工具,这使得查找潜在的内存泄漏变得容易得多。

由于人们在建议使用几种工具(自从在JDK和JProbe试用版中获得该工具以来,我只尝试了可视化wm),尽管我应该建议在Eclipse平台上构建的免费/开源工具,即Memory Analyzer(有时也称为SAP内存)分析器),网址为http://www.eclipse.org/mat/

这个工具真正很棒的地方是它在我第一次打开它时就索引了堆转储,这使它可以显示保留的堆之类的数据,而不必为每个对象等待5分钟(几乎所有操作都比我尝试过的其他工具快了很多) 。

打开转储时,第一个屏幕将显示一个饼图,其中包含最大的对象(计算保留的堆),并且可以快速导航到较大的对象以增加舒适度。它还具有“发现可能泄漏的嫌疑犯”,我认为可以派上用场,但是由于导航对我来说足够了,所以我没有真正涉足。


1
值得注意的是:显然在Java 5及更高版本中,HeapDumpOnCtrlBreakVM参数不可用。我发现的解决方案(到目前为止,仍在寻找)是使用JMap转储.hprof文件,然后将其放入Eclipse中并使用MAT进行检查。
2012年

1
关于获取堆转储,大多数分析器(包括JVisualVM)都包含将堆和线程都转储到文件的选项。
bbaja42'1

13

工具是很大的帮助。

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

在这种情况下,它有助于您了解hprof转储文件的方式。

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

如果您检查了分配站点,但没有发现任何错误,则必须开始从某些活动对象到根对象的向后链接,以查找意外的参考链。这是工具真正有用的地方,但是您可以手工完成相同的操作(使用grep)。不仅有一个根对象(即不受垃圾回收的对象)。线程,类和堆栈框架充当根对象,它们强烈引用的任何内容均不可收集。

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

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


“堆转储是如此之大崩溃的工具” -最后我检查,jhat并且MAT显然是试图将整个堆转储加载到内存中,因此通常与崩溃OutOfMemoryError的大垃圾场(即,从最需要堆分析的应用程序! )。NetBeans Profiler似乎对索引引用使用了不同的算法,这种索引在大型转储时可能会变慢,但至少不会消耗工具中的无限内存并导致崩溃。
杰西·格里克

10

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

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

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

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

这就是类直方图出现的地方。您可以使用jmap工具转储实时类的直方图。这只会给出类的内存使用情况的直方图,基本上不会有链接参考的信息。例如,可以将char数组放在顶部。和String类在下面的某个地方。您必须自己绘制连接。

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

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

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

jstat -gccause pid <optional time interval>

有关-jhat,jmap,Full GC,Humongous分配,G1GC的其他Google参考


1
添加一个博客帖子有更多的细节在这里- alexpunnen.blogspot.in/2015/06/...
亚历Punnen

5

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

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


4

嗯,总是存在一种技术含量低的解决方案,即在修改地图时添加地图大小的日志记录,然后在日志中搜索那些地图超出合理大小的地图。



0

您确实需要使用可跟踪分配的内存分析器。看一下JProfiler-他们的“堆助行器”功能很棒,并且它们与所有主要的Java IDE集成在一起。它不是免费的,但也不是那么昂贵(单个许可证499美元)-您将很快花费500美元的时间,用较不复杂的工具苦苦寻找漏洞。


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

请注意,有时通过某些操作(例如流和套接字)释放内存会花费一些时间。您不应该根据第一个输出来判断,而应该在特定的时间内对其进行测试。


0

签出此屏幕有关使用JProfiler查找内存泄漏。这是@Dima Malenko答案的直观说明。

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


0

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

Eclipse的MAT是Eclipse IDE提供的工具来分析一组插件heap dumps从Java应用程序,并确定memory problems在应用程序中。

这有助于开发人员使用以下功能来发现内存泄漏

  1. 获取内存快照(堆转储)
  2. 直方图
  3. 保留堆
  4. 统治者树
  5. 探索GC根源的路径
  6. 检验员
  7. 通用记忆体反模式
  8. 对象查询语言

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.