查找内存泄漏


区分正常的内存使用和内存泄漏往往不是很容易。然而,过度使用内存和内存泄漏都有相同的症状,因此可以用同样的方法进行分析。 分析分两步进行: 定位可疑对象并找出为什么这些对象还在堆上。

查找新对象

当一个有内存泄漏的应用程序在运行时,它会随着时间的推移消耗越来越多的内存。 检测内存使用量的增长最好使用VM遥测和"所有对象"和"记录的对象"视图中的差值功能。 通过这些视图,你可以确定是否存在问题以及问题的严重程度。有时,实例表中的差值列已经可以让你知道问题所在。

任何对内存泄漏的深入分析都需要堆遍历器中的功能。围绕一个特定用例要详细调查内存泄漏, "标记堆"功能是最合适的。 它可以让你识别出自之前某个特定时间点以来留在堆上的新对象。对于这些对象,你必须检查它们是否仍然合法地在堆上, 或者是否有一个错误的引用使它们继续存在,即使该对象没有进一步的用途。

另一种隔离你感兴趣的对象集的方法是通过分配记录。在生成堆快照时,你可以选择显示所有记录的对象。 然而,你可能不想将分配记录仅仅限于一个特定的用例。此外,分配记录有很高的开销。 标记堆操作的影响相对要小得多。最后,如果你标记了堆,堆遍历器允许你在任何选择步骤中使用 顶部的使用新的使用旧的超链接。

分析最大对象

如果内存泄漏填满了可用的堆,它将使分析应用程序中的其他类型的内存使用相形见绌。在这种情况下,你不需要检查新的对象, 而只需要分析哪些对象是最重要的。

内存泄漏的速度可能非常缓慢,而且可能在很长一段时间内都不会成为主要的内存消耗。对这样的内存泄漏进行分析, 直到它变得明显为止,可能并不现实。通过JVM中内置的设施,当一个OutOfMemoryError 被抛出时, 自动保存一个HPROF快照, 你可以得到一个内存泄漏比常规内存消耗更重要的快照。事实上,最好的办法是总是添加

-XX:+HeapDumpOnOutOfMemoryError

到VM参数或生产系统中,这样你就有办法分析在开发环境中可能难以重现的内存泄漏。

如果内存泄漏很明显,那么堆遍历器中"最大对象"视图中的顶层对象将包含被错误保留的内存。 虽然最大的对象本身可能是合法的对象,但打开它们的支配树将发现泄漏的对象。 在简单的情况下,单一对象包含大部分的堆。例如,如果一个Map被用来缓存对象,而这个缓存从未被清除, 那么这个Map就会出现在最大对象的支配者树中。

查找源自垃圾回收器根的强引用链

一个对象只有是强引用的情况下才会是一个问题。"强引用",意味着至少有一个垃圾回收器根到该对象的引用链。 "垃圾回收器"根(简称GC根)是垃圾回收器知道的JVM中的特殊引用。

要查找源自GC根的引用链,你可以使用"传入引用"视图或堆遍历器图中的显示到GC根的路径操作。 这样的引用链在实践中可能会很长,所以通常在"传入引用"视图中可以更容易呈现。引用链从底层指向顶层的对象。 只有作为搜索结果的引用链才会被展开,同一层次上的其他引用是不可见的,直到一个节点被关闭,再打开或者是 调用上下文菜单显示所有传入引用操作。

要获得对引用节点中使用的GC根的类型和其他术语的解释,请使用树图例。

当你在树中选择了节点,非模态树图例会突出显示所选节点中所有使用的图标和术语。在对话框中点击行,会在底部显示解释。

垃圾回收器根的重要类型是来自堆栈的引用,本地代码通过JNI创建的引用,以及当前正在使用的活动线程和对象Monitor等资源。 此外,JVM在适当位置还加入了一些"粘性"引用来保持重要系统。

类和类加载器有一个特殊的循环引用策略。当类和它的类加载器出现以下情况时,它们会被垃圾回收:

在大多数情况下,类是通往你感兴趣的GC根的最后一步。类本身不是GC根。然而,在所有没有使用自定义类加载器的情况下, 会将其视为GC根。这是JProfiler在搜索垃圾回收器根时的默认模式,但你可以在到根的路径选项对话框中更改它。

如果你在呈现通往GC根的最短路径时遇到问题,你可以搜索其他路径。一般情况下,不建议搜索到GC根的所有路径, 因为这会产生大量的路径。

与实时内存视图相反,堆遍历器从不显示未引用的对象。然而,堆遍历器可能不仅仅显示强引用对象。 默认情况下,堆遍历器也会保留仅由软引用引用的对象,但会排除仅由弱引用、虚引用或finalizer引用引用的对象。 因为软引用除非堆用尽,否则不会被垃圾回收,所以将其包括在内,因为否则你可能无法解释大堆的使用情况。 在生成堆快照时显示的选项对话框中,你可以调整这种行为。

在堆遍历器中拥有弱引用对象可能会对调试目的有用。如果稍后想删除弱引用对象,可以使用"删除弱引用保留的对象"检查。

在搜索到GC根的路径时,会考虑在堆遍历器选项对话框中选择的保留对象的引用类型。 这样一来,到GC根的路径搜索总是可以解释为什么对象被保留在堆遍历器中。 在到GC根的路径搜索的选项对话框中,你可以将可接受的引用类型扩大到所有弱引用。

排除整个对象集

到目前为止,我们只关注了单个对象。通常情况下,你会有许多相同类型的对象成为内存泄漏的一部分。 在许多情况下,对单个对象的分析也会对当前对象集的其他对象有效。对于更普遍的情况,即感兴趣的对象以不同的方式被引用, "合并支配引用"视图将帮助你找出哪些引用负责在堆上持有当前对象集。

支配引用树中的每个节点都会告诉你,如果你删除了该引用,当前对象集中有多少对象将可以被垃圾回收。 被多个垃圾回收器根引用的对象可能没有任何支配传入引用,因此该视图可能只能帮助你处理一部分对象,甚至可能是空的。 在这种情况下,你必须使用合并传入引用视图,并逐一排除垃圾回收器根。