JProfiler帮助文档

内存分析 (Memory profiling)

获取堆中对象信息有两种方法。一方面,分析代理可以跟踪每个对象的分配和垃圾回收。在 JProfiler 中,这称为“分配记录 (allocation recording)”。它告诉您对象被分配的位置,还可以用于创建关于临时对象的统计数据。另一方面,JVM 的分析接口允许分析代理获取“堆快照 (heap snapshot)”,以便检查所有活动对象及其引用。此信息对于理解为什么对象无法被垃圾回收是必需的。

分配记录和堆快照都是昂贵的操作。分配记录对运行时特性有很大影响,因为必须对 java.lang.Object 构造函数进行插装,并且垃圾收集器必须不断向分析接口报告。这就是为什么默认情况下不记录分配的原因,您必须显式地 启动和停止记录。获取堆快照是一次性操作。然而,它可能会暂停 JVM 几秒钟,并且获取的数据分析可能需要相对较长的时间,随着堆的大小而变化。

JProfiler 将其内存分析分为两个视图部分:“实时内存 (Live memory)”部分呈现可以定期更新的数据,而“堆漫游器 (Heap walker)”部分显示静态堆快照。分配记录在“实时内存”部分中控制,但记录的数据也由堆漫游器显示。

内存分析可以解决的三个最常见问题是:查找内存泄漏 (memory leak)、减少内存消耗和减少临时对象的创建。对于前两个问题,您将主要使用堆漫游器,主要通过查看 JVM 中最大的对象由谁持有以及它们在哪里创建。对于最后一个问题,您只能依赖显示记录分配的实时视图,因为它涉及到已经被垃圾回收的对象。

跟踪实例计数 (Tracking instance counts)

为了概览堆中的对象,“所有对象 (All objects)”视图向您显示所有类及其实例计数的直方图。此视图中显示的数据不是通过分配记录收集的,而是通过执行仅计算实例计数的小型堆快照收集的。堆越大,执行此操作所需的时间就越长,因此此视图不会自动更新为当前值。

在寻找内存泄漏时,您通常希望随时间比较实例计数。要对所有类执行此操作,您可以使用视图的差异功能。当同时选择所有对象的两个转储时,会插入一个 差异 (Difference) 列,实例计数的直方图显示标记时的基线值,以绿色显示。当获取所有对象的新转储时,最旧的选定转储将保持选中状态,并显示与所有对象的新转储的差异。

转储选择器显示转储获取时的时间戳。通过双击转储,您可以添加标签以便于识别。所有对象的转储也可以通过 触发器动作 (trigger action)Controller API 触发,其中也可以指定标签。

另一方面,“记录的对象 (Recorded objects)”视图仅显示在您开始分配记录后分配的对象的实例计数。当您停止分配记录时,不会添加新的分配,但垃圾回收会继续被跟踪。通过这种方式,您可以查看在某个用例中哪些对象保留在堆上。请注意,可能很长时间内对象不会被垃圾回收。使用 运行 GC (Run GC) 工具栏按钮可以加速此过程。对于大多数动态更新的视图,冻结 (Freeze) 工具栏按钮可用于停止更新显示的数据。

使用 标记当前 (Mark Current) 工具栏按钮,您还可以在“记录的对象”视图中显示相对于选定基线的差异列。对于选定的类,您还可以通过上下文菜单中的 将选择添加到类追踪器 (Add Selection to Class Tracker) 操作显示时间解析的图形。

分配点 (Allocation spots)

当分配记录处于活动状态时,每次分配对象时,JProfiler 都会记录调用栈。它不使用精确的调用栈,例如,来自堆栈行走 API,因为那样会非常昂贵。相反,使用与 CPU 分析配置相同的机制。这意味着调用栈根据 调用树过滤器 (call tree filters) 进行过滤,并且实际的分配点可能位于调用栈中不存在的方法中,因为它来自被忽略或紧凑过滤的类。然而,这些变化直观上很容易理解:紧凑过滤的方法负责在进一步调用紧凑过滤的类时进行的所有分配。

如果您使用采样,分配点会变得近似并可能令人困惑。与时间测量不同,您通常对某些类可以在哪里分配以及不能在哪里分配有明确的想法。由于采样描绘的是统计而非精确的图景,您可能会看到看似不可能的分配点,例如 java.util.HashMap.get 分配您自己的类。对于任何需要精确数字和调用栈的分析,建议将分配记录与插装一起使用。

就像 CPU 分析一样,分配调用栈以调用树的形式呈现,只不过是分配计数和分配内存,而不是调用计数和时间。与 CPU 调用树不同,分配调用树不会自动显示和更新,因为树的计算更昂贵。JProfiler 不仅可以为所有对象显示分配树,还可以为选定的类或包显示。结合其他选项,这在您要求 JProfiler 从当前数据计算分配树后显示的选项对话框中配置。

CPU 调用树的一个有用特性是您可以从上到下跟随累积时间,因为每个节点都包含在子节点中花费的时间。默认情况下,分配树的行为相同,这意味着每个节点都包含子节点进行的分配。即使分配仅由调用树深处的叶节点执行,数字也会向上传播到顶部。通过这种方式,您可以始终看到在打开分配调用树的分支时值得调查的路径。“自分配 (Self-allocations)”是指实际上由节点而不是其后代执行的分配。与 CPU 调用树一样,百分比条以不同的颜色显示它们。

在分配调用树中,通常有很多节点没有执行任何分配,特别是当您为选定的类显示分配时。这些节点仅用于向您显示实际分配发生的节点的调用栈。这些节点在 JProfiler 中称为“桥接 (bridge)”节点,并以灰色图标显示,如上面的屏幕截图所示。在某些情况下,分配的累积可能会妨碍您,您只想看到实际的分配点。分配树的视图设置对话框提供了一个选项,用于显示未累积的数字。如果激活,桥接节点将始终显示零分配并且没有百分比条。

分配热点视图与分配调用树一起填充,允许您直接关注负责创建选定类的方法。与记录的对象视图一样,分配热点视图支持标记当前状态并观察随时间的差异。视图中添加了一个差异列,显示自 标记当前值 (Mark Current Values) 操作调用以来热点的变化。由于分配视图默认情况下不会定期更新,您必须单击 计算 (Calculate) 工具栏按钮以获取新数据集,然后与基线值进行比较。自动更新在选项对话框中可用,但不建议用于大堆大小。

分配记录率 (Allocation recording rate)

记录每一次分配都会增加显著的开销。在许多情况下,分配的总数并不重要,相对数字足以解决问题。这就是为什么 JProfiler 默认只记录每第 10 次分配。这将开销减少到记录所有分配的约 1/10。如果您希望记录所有分配,或者即使更少的分配也足以满足您的目的,您可以在记录的对象视图以及分配调用树和热点视图的参数对话框中更改记录率。

此设置也可以在会话设置对话框的“高级设置->内存分析 (Advanced Settings->Memory profiling)”步骤中找到,可以为离线分析会话进行调整。

分配记录率会影响“记录的对象 (Recorded objects)”和“记录的吞吐量 (Recorded throughput)”的 VM 遥测 (telemetries),其值将按配置的分数进行测量。当 比较快照 (comparing snapshots) 时,将报告第一个快照的分配率,并根据需要相应缩放其他快照。

分析已分配的类 (Analyzing allocated classes)

在计算分配树和分配热点视图时,您必须指定要预先查看其分配的类或包。如果您已经专注于特定类,这很好用,但在没有任何先入之见的情况下尝试查找分配热点时不方便。一种方法是开始查看“记录的对象 (Recorded objects)”视图,并使用上下文菜单中的操作切换到选定类或包的分配树或分配热点视图。

另一种方法是从所有类的分配树或分配热点开始,并使用 显示类 (Show classes) 操作显示选定分配点或分配热点的类。

已分配类的直方图显示为 调用树分析 (call tree analysis)。此操作也适用于其他调用树分析。

类分析视图是静态的,当重新计算分配树和热点视图时不会更新。重新加载分析 (Reload Analysis) 操作将首先更新分配树,然后从新数据重新计算当前分析视图。

分析垃圾回收的对象 (Analyzing garbage collected objects)

分配记录不仅可以显示活动对象,还可以保留垃圾回收对象的信息。这在调查临时分配时很有用。分配大量临时对象会产生显著的开销,因此通常减少分配率会显著提高性能。

要在记录的对象视图中显示垃圾回收的对象,请将活性 (liveness) 选择器更改为 垃圾回收的对象 (Garbage collected objects)活动和垃圾回收的对象 (Live and garbage collected objects)。分配调用树和分配热点视图的选项对话框中有一个等效的下拉菜单。

然而,JProfiler 默认情况下不会为垃圾回收的对象收集分配树信息,因为仅维护活动对象的数据可以大大减少开销。当在“分配调用树 (Allocation Call Tree)”或“分配热点 (Allocation Hotspots)”视图中将活性选择器切换到包含垃圾回收对象的模式时,JProfiler 建议更改记录类型。这是分析设置中的更改,因此如果您选择立即应用更改,所有先前记录的数据将被清除。如果您想提前更改此设置,可以在会话设置对话框中的“高级设置 (Advanced Settings)”->“内存分析 (Memory Profiling)”中进行。

下一站:堆漫游器 (Next stop: heap walker)

任何更高级的问题都将涉及对象之间的引用。例如,记录的对象、分配树和分配热点视图中显示的大小是 浅大小 (shallow sizes)。它们仅包括类的内存布局,而不包括任何引用的类。要查看一个类的对象有多重,您通常想知道 保留大小 (retained size),即如果这些对象从堆中移除,将释放的内存量。

这种信息在实时内存视图中不可用,因为它需要枚举堆中的所有对象并执行昂贵的计算。这个工作由堆漫游器处理。要从实时内存视图中的兴趣点跳转到堆漫游器,可以使用 在堆漫游器中显示 (Show in Heap Walker) 工具栏按钮。它会将您带到堆漫游器中的等效视图。

如果没有可用的堆快照,将创建一个新的堆快照,否则 JProfiler 会询问您是否使用现有的堆快照。

无论如何,重要的是要理解实时内存视图和堆漫游器中的数字通常会非常不同。除了堆漫游器显示的快照与实时内存视图在不同时间点之外,它还消除了所有未引用的对象。根据垃圾收集器的状态,未引用的对象可能占据堆的很大一部分。