区分常规内存使用与内存泄漏通常并不简单。然而,过度的内存使用与内存泄漏具有相同的症状,因此可以通过相同的方式进行分析。分析分两步进行:定位可疑对象,以及找出这些对象仍留在堆中的原因。
查找新对象
当存在内存泄漏的应用程序运行时,它会随着时间的推移消耗越来越多的内存。 检测内存使用增长的最佳方法是使用 VM 遥测 (telemetry) 以及“所有对象”和“已记录对象”视图 (view) 中的 差异对比功能。通过这些视图 (view),您可以确定是否存在问题以及问题的严重程度。有时,类直方图表中的差异列就能让您大致了解问题所在。
对内存泄漏进行任何更深入的分析都需要使用 Heap Walker 中的功能。要详细调查围绕特定用例的内存泄漏, “标记堆”功能最为适用。它允许您识别自之前某个特定时间点以来一直保留在堆上的新对象。对于这些对象,您必须检查它们是否仍合理地存在于堆中,或者是否存在错误的引用使其保持存活,即使该对象已不再起任何作用。
隔离您感兴趣的对象集的另一种方法是通过分配 (allocation) 记录。 在获取堆快照时,您可以选择显示所有已记录的对象。但是,您可能不希望将分配 (allocation) 记录仅限制在某个特定用例中。此外,分配 (allocation) 记录的开销较高,因此 Mark Heap 操作造成的影响相对小得多。最后,如果您已经标记了堆,Heap Walker 允许您在任何选择步骤中通过标题栏中的 Use new 和 Use old 超链接选择新旧对象。
分析最大对象
如果内存泄漏填满了可用堆,它将使被分析 (profiled) 应用程序中的其他类型内存使用相形见绌。在这种情况下,您不必检查新对象,只需分析哪些对象最重要即可。
内存泄漏的速率可能非常缓慢,在很长一段时间内可能都不会占据主导地位。在内存泄漏变得明显之前对其进行分析可能并不切合实际。利用 JVM 在抛出 OutOfMemoryError 时自动
保存 HPROF 快照的内置功能,您可以获得一个内存泄漏比常规内存消耗更占主导地位的快照。事实上,始终将
-XX:+HeapDumpOnOutOfMemoryError
添加到生产系统的 VM 参数中是一个好习惯,这样您就可以分析在开发环境中可能难以复现的内存泄漏。
如果内存泄漏占据主导地位,Heap Walker 的“最大对象”视图 (view) 中排在最前面的对象将包含被错误保留的内存。虽然最大对象本身可能是合法的对象,但展开它们的支配树 (dominator tree) 将找到发生泄漏的对象。在简单情况下,存在一个包含大部分堆的单个对象。例如,如果使用 Map 来缓存对象而该缓存从未被清理,那么该 Map 就会出现在最大对象的支配树中。
查找来自垃圾回收器根的强引用链
一个对象只有在被强引用时才会成为问题。“被强引用”意味着从垃圾回收器根到该对象至少存在一条引用链。“垃圾回收器根”(简称 GC root)是 JVM 中垃圾回收器已知的特殊引用。
要查找来自 GC root 的引用链,可以使用“传入引用”视图 (view) 或 Heap Walker 图形 (graph) 中的 Show Paths To GC Root 操作。此类引用链在实践中可能会非常长,因此在“传入引用”视图 (view) 中通常更容易解读。这些引用从底部指向顶层的对象。搜索结果中仅展开作为结果的引用链,同一级别上的其他引用不可见,直到关闭并再次打开节点,或者调用上下文菜单中的 Show All Incoming References 操作。
要获取引用节点中使用的 GC root 类型及其他术语的解释,请使用树图例。
当您在树中选择节点时,非模态树图例会突出显示所选节点中使用的所有图标和术语。点击对话框中的某一行将在底部显示说明。
垃圾回收器根的重要类型包括来自栈的引用、由本地代码通过 JNI 创建的引用,以及当前正在使用的资源(如活动线程和对象 Monitor)。此外,JVM 还会添加一些“粘性”引用来维持重要系统的运行。
类和类加载器 (classloader) 具有特殊的循环引用机制。当满足以下条件时,类将与其类加载器 (classloader) 一起被垃圾回收:
- 该类加载器 (classloader) 加载的所有类都没有任何活动实例
- 除类加载器自身的类外,该类加载器 (classloader) 本身未被其他任何对象引用
-
除在该类加载器 (classloader) 的上下文中外,没有任何
java.lang.Class对象被引用
在大多数情况下,类是您所关注的通往 GC root 路径上的最后一步。类本身并不是 GC root。然而,在所有不使用自定义类加载器 (classloader) 的情况下,将它们视作 GC root 是恰当的。这是 JProfiler 在搜索垃圾回收器根时的默认模式,但您可以在通往根的路径选项对话框中进行更改。
如果您在解读通往 GC root 的最短路径时遇到困难,可以搜索其他路径。通常不建议搜索通往 GC root 的所有路径,因为这可能会产生大量的路径。
与活动内存视图 (view) 不同,Heap Walker 从不显示未被引用的对象。但是,Heap Walker 可能不仅显示强引用的对象。默认情况下,Heap Walker 还会保留仅由软引用引用的对象,但会排除仅由弱引用、虚引用或终结器引用引用的对象。因为软引用只有在堆耗尽时才会被垃圾回收,所以它们会被包含在内,否则您可能无法解释较大的堆使用量。在获取堆快照时显示的选项对话框中,您可以调整此行为。
在 Heap Walker 中保留弱引用的对象可能有助于调试。如果您随后想要移除弱引用保留的对象,可以使用“移除弱引用保留的对象”检查。
在搜索通往 GC root 的路径时,在 Heap Walker 选项对话框中选择用于保留对象的引用类型会被纳入考虑。通过这种方式,通往 GC root 的路径搜索始终可以解释为什么某个对象被保留在 Heap Walker 中。在通往 GC root 路径搜索的选项对话框中,您可以将可接受的引用类型扩大到所有弱引用。
消除整个对象集
到目前为止,我们只观察了单个对象。通常,内存泄漏中会包含许多相同类型的对象。在许多情况下,对单个对象的分析也同样适用于当前对象集 (object set) 中的其他对象。对于更普遍的情况,即感兴趣的对象以不同方式被引用,“合并支配引用”视图 (view) 将帮助您找出负责将当前对象集 (object set) 保留在堆上的引用。
支配引用树中的每个节点都会告诉您,如果消除该引用,当前对象集 (object set) 中将有多少个对象符合垃圾回收的条件。被多个垃圾回收器根引用的对象可能没有任何支配性传入引用,因此该视图 (view) 可能只能帮您处理一小部分对象,甚至可能为空。在这种情况下,您必须使用合并传入引用视图 (view) 并逐个消除垃圾回收器根。

























