如何找到Java内存泄漏

如何找到Java内存泄漏

How to find a Java Memory Leak

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

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

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


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

  • 当所有初始化完成且应用程序处于空闲状态时,启动应用程序并等待直到其进入"稳定"状态。
  • 多次运行怀疑会导致内存泄漏的操作,以允许发生任何与数据库相关的高速缓存。
  • 运行GC并获取内存快照。
  • 再次运行该操作。根据操作的复杂性和已处理数据的大小,可能需要运行几次到很多次。
  • 运行GC并获取内存快照。
  • 对2个快照运行差异并进行分析。
  • 从根本上说,分析应该从最大的正差异开始,例如对象类型,然后找出导致这些额外对象滞留在内存中的原因。

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

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


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

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

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

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


    工具是很大的帮助。

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

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

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

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

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

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


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

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

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

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

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

    1
    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行为

    1
    jstat -gccause pid <optional time interval>

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


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

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


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


    NetBeans具有内置的探查器。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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");
        }

    }

    如果输出数量相等,则应用程序中不会发生内存泄漏,但是如果您看到内存使用量之间的差异(数量增加),则项目中将存在内存泄漏。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    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

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


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


    推荐阅读