如何找到Java内存泄漏

时间:2008-09-02 17:37:12

标签: java memory memory-leaks jhat

如何在Java中找到内存泄漏(例如,使用JHat)?我试图在JHat中加载堆转储以获得基本外观。但是,我不明白我应该如何找到根引用(ref)或其所谓的。基本上,我可以说有几百兆字节的哈希表条目([java.util.HashMap $ Entry或类似的东西),但地图遍布整个地方...有没有办法搜索大地图,或者可能找到大型对象树的一般根源?

[编辑] 好吧,到目前为止我已经阅读了答案,但我们只是说我是一个廉价的混蛋(这意味着我更感兴趣的是学习如何使用JHat而不是支付JProfiler)。此外,JHat始终可用,因为它是JDK的一部分。除非当然没有办法与JHat相提并论蛮力,但我无法相信情况可能如此。

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

13 个答案:

答案 0 :(得分:121)

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

  1. 启动应用程序并等待它进入“稳定”状态,此时所有初始化完成并且应用程序处于空闲状态。
  2. 多次运行怀疑产生内存泄漏的操作,以允许任何缓存,与DB相关的初始化。
  3. 运行GC并拍摄内存快照。
  4. 再次运行操作。根据操作的复杂性和处理的数据大小,操作可能需要运行几次到多次。
  5. 运行GC并拍摄内存快照。
  6. 为2个快照运行差异并进行分析。
  7. 基本上,分析应该从对象类型的最大正差异开始,并找出导致这些额外对象留在内存中的原因。

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

    我做了很多专门用于减少应用程序内存占用的项目,这种一般方法通过一些特定于应用程序的调整和技巧总是运行良好。

答案 1 :(得分:48)

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

由于人们建议使用多种工具(我在JDK和JProbe试用版中仅尝试了Visual Wm)我虽然建议在Eclipse平台上构建一个免费/开源工具,但内存分析器(有时也称为{内存分析器}可在http://www.eclipse.org/mat/上找到。

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

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

答案 2 :(得分:12)

工具是一个很大的帮助。

但是,有时您无法使用工具:堆转储非常庞大而导致工具崩溃,您正试图在某些生产环境中对您只有shell访问权限的机器进行故障排除等。 / p>

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

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

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

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

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

答案 3 :(得分:10)

大多数情况下,在企业应用程序中,给定的Java堆大于最大12到16 GB的理想大小。我发现很难让NetBeans探查器直接在这些大型Java应用程序上运行。

但通常不需要这样做。您可以使用jdk附带的jmap实用程序来获取" live"堆转储,即jmap将在运行GC后转储堆。在应用程序上进行一些操作,等到操作完成,然后再采取另一个" live"堆转储。使用像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类与应用程序类相关联。这将提供一个非常好的提示。这是一个pythons脚本,可以帮助您比较两个jmap直方图转储。 histogramparser.py

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

jstat -gccause pid <optional time interval>

google for-jhat,jmap,Full GC,Humongous allocation,G1GC

的其他推介

答案 4 :(得分:5)

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

使用HashTables,Hashmaps或类似方法是您可以在Java中完全泄漏内存的少数几种方法之一。如果我不得不手工找到泄漏,我会用peryically打印我的HashMaps的大小,然后从那里找到我添加项目的地方,忘记删除它们。

答案 5 :(得分:4)

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

答案 6 :(得分:1)

NetBeans有一个内置的分析器。

答案 7 :(得分:0)

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

答案 8 :(得分: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

注意,有时通过流和套接字之类的操作来释放内存会花费一些时间。您不应该通过第一批输出来判断,而应该在特定的时间内对其进行测试。

答案 9 :(得分:0)

请查看有关screen cast的有关使用JProfiler查找内存泄漏的信息。 这是@Dima Malenko答案的直观说明。

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

答案 10 :(得分:0)

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

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

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

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

enter image description here

答案 11 :(得分:0)

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

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

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

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

选项:观察堆空间 enter image description here

如果你看到堆空间一直在增加,那么肯定存在内存泄漏。

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

enter image description here

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

    jmap -histo <pid> > histo1.txt
    

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

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

答案 12 :(得分:-1)

您可能需要查看jconsole。它也是JDK的一部分,我发现与jhat一起查找内存/引用泄漏很有帮助。另请查看this博客条目。