JProfiler帮助文档

内存分析


有两种方式可以获得堆上对象的信息。一方面,分析代理可以跟踪每个对象的分配和垃圾回收情况。 在JProfiler中,这被称为"分配记录"。它会告诉你对象被分配的位置,也可以用来创建有关临时对象的统计。 另一方面,JVM的分析接口允许分析代理生成"堆快照",以便检查所有活动对象及其引用。 这些信息对于理解为什么对象不能被垃圾回收不可缺少。

分配记录和堆快照都是成本很高的操作。分配记录对运行时的特性有很大的影响, 因为java.lang.Object 构造函数必须被插入指令(Instrument),而垃圾回收器必须不断向分析接口报告。 这就是为什么默认情况下不记录分配,你必须显式地开始和停止记录。 生成堆快照是一次性操作。然而,它可能会使JVM暂停几秒钟,并且对获取的数据进行分析可能需要相对较长的时间,堆越大需要的时间越长。

JProfiler将其内存分析分为两个视图部分:"实时内存"部分显示的是可以定期更新的数据,而"堆遍历器"部分显示的是静态的堆快照。 分配记录是在"Live memory"部分控制的,但记录的数据也在堆遍历器中显示。

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

跟踪实例数

为了了解堆上有哪些对象,"所有对象"视图向你显示了所有类及其实例计数的直方图。 该视图中显示的数据不是通过分配记录收集的,而是通过执行一个迷你堆快照,只计算实例计数。 堆越大,执行该操作所需的时间就越长,因此视图的更新频率会根据测量的开销自动降低。 当视图不活动时,不收集数据,视图不会产生任何开销。与大多数动态更新的视图一样, 一个 冻结工具栏按钮可以停止更新显示的数据。

而"记录的对象"视图,只显示你启动分配记录后被分配的对象实例计数。当你停止分配记录时,不会添加新的分配,但会继续跟踪垃圾回收。 通过这种方式,你可以看到某个用例的堆上还剩下哪些对象。注意,对象可能在很长一段时间内都不会被垃圾回收。 通过运行GC工具栏按钮,你可以加快这个过程。

当寻找内存泄漏时,你通常想比较一段时间内的实例数。要对所有的类进行比较,可以使用视图的差值功能。 使用 标记当前工具栏按钮,会插入一个相差列,实例计数的直方图以绿色显示标记时的基线值。

对于所选类,你还可以通过使用上下文菜单中的将所选内容添加到类跟踪器操作显示一个时间解析图。

分配点

当分配记录处于激活状态时,JProfiler会在每次分配对象时记录调用堆栈。它不会使用确切的调用堆栈,例如来自堆遍历API的调用堆栈, 因为那样成本太高。而是使用与CPU分析配置相同的机制。这意味着调用堆栈会根据调用树过滤器进行过滤, 而且实际的分配点可能在调用堆栈中没有显示的方法中,因为它来自一个被忽略(Ignored)或压缩(Compact)过滤的类。 然而,这些变化直观上很容易理解:一个压缩(Compact)过滤方法负责所有进一步调用压缩(Compact)过滤类的分配。

如果你使用采样,分配点就会变得粗略估计不准确,可能会令人困惑。不像时间测量,你通常会清楚地知道某些类在哪里可以分配,哪里不能分配。 因为采样描绘的是统计而不是精确的图,你可能会看到一些看似不可能的分配点, 比如java.util.HashMap.get分配你自己的一个类。对于任何一种对精确数字和调用堆栈很重要的分析, 建议将分配记录和Instrumentation一起使用。

就像CPU分析一样,分配调用堆栈也是以调用树的形式显示的,只是显示的是分配计数和分配的内存,而不是调用计数和时间。 与CPU调用树不同的是,分配调用树不会自动显示和更新,因为该树的计算成本较高。JProfiler不仅可以显示所有对象的分配树, 还可以显示所选类或包的分配树。与其他选项一起,这在你要求JProfiler从当前数据计算分配树后显示的选项对话框中进行配置。

CPU调用树的一个有用的特性是,你可以沿着从上到下累计的时间,因为每个节点都包含了子节点所花费的时间。 默认情况下,分配树的行为也是如此,这意味着每个节点都包含了子节点进行的分配。即使分配只由调用树深处的叶节点执行, 数字也会向上传递到顶部。这样一来,你在打开分配调用树的分支时,总能看到哪条路径值得调查。 "自身分配"是指那些实际由节点而不是由其子节点执行的分配。像在CPU调用树中一样,百分比栏用不同的颜色显示它们。

在分配调用树中,往往有很多节点完全没有执行分配,特别是当你为一个选定类显示分配时。 这些节点存在只是为了向你展示到实际执行分配节点的完整调用堆栈路径。这样的节点在JProfiler中被称为"桥接"节点, 并以灰色图标显示,正如你在上面的截图中看到的那样。在某些情况下,分配的累积可能会妨碍你,因为你只想看到实际的分配点。 为此,分配树的视图设置提供了一个选项以显示非累计数。如果激活,桥接节点将始终显示零分配,并且没有百分比条。

分配热点视图与分配调用树一起,允许你直接关注负责创建所选类的方法。就像记录的对象视图,分配热点视图也支持标记当前状态和 观察一段时间内的差值。视图中会添加一个差值列,它显示了热点自当标记当前值 操作被调用后的变化。因为默认情况下,分配视图不会定期更新,所以你必须单击计算工具栏按钮以获得一个新数据集然后与基线值比较。 在选项对话框中可以有一个自动更新更新选项可用,但对于大堆,不建议使用。

分配记录率

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

该设置也可以在会话设置对话框的"高级设置->内存分析"步骤中找到,在那里可以为离线分析会话进行调整。

分配记录率会影响"记录的对象"和"记录吞吐量"VM遥测,这些值将以配置的分数来测量。 比较快照时,将报告第一个快照的分配率,如有必要,将对其他快照进行相应的缩放。

分析分配的类

当计算分配树和分配热点视图时,你必须预先指定你想看到其分配的类或包。如果你已经在关注特定类,这工作的很好。 但当你想在没有任何预设的情况下查找分配热点时,就不方便了。一种方式是从"记录的对象"视图开始查看, 然后使用上下文菜单中的操作切换到所选类或包的分配树或分配热点视图。

另一种方式是从所有类的分配树或分配热点开始,用显示类操作来显示所选分配点或分配热点的类。

分配的类的直方图显示为调用树分析。这个操作对于其他调用树分析也有效。

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

分析垃圾回收对象

分配记录不仅可以显示实时对象,还可以保留垃圾回收对象的信息。这在调查临时分配时很有用。分配大量的临时对象会产生很大的开销, 所以降低分配率往往能大大改善性能。

要在记录的对象视图中显示垃圾回收对象,请将活性选择器更改为垃圾回收对象活动对象和垃圾回收对象。 分配调用树和分配热点视图的选项对话框有一个类似的下拉菜单。

但是,JProfiler 默认情况下并不收集垃圾回收对象的分配树信息,因为只维护活动对象的数据可以减少很多开销。 当将"分配调用树"或"分配热点"视图中的活性选择器切换到包括垃圾回收对象的模式时,JProfiler建议更改记录类型。 这是分析设置的一个改变,因此如果你选择立即应用该改变,所有以前记录的数据将被清除。 如果你想提前更改这个设置,可以在会话设置对话框的"高级设置"->"内存分析"中进行更改。

下一站:堆遍历器

任何深入一点的问题都会涉及到对象之间的引用。例如,在记录的对象、分配树和分配热点视图中显示的大小都是浅层大小, 它们只是包括了类的内存布局,但不包括任何引用的类。要想知道一个类的对象到底有多大,你通常需要知道保留大小, 也就是如果从堆中删除这些对象所释放的内存量。

这种信息在实时内存视图中是没有的,因为它需要枚举堆上的所有对象并执行成本昂贵的计算。这项工作由堆遍历器处理。 要从实时内存视图中的感兴趣点跳转到堆遍历器中,可以使用在堆遍历器中显示所选内容工具栏按钮。 它会将你带入堆遍历器中的相应视图。

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

在任何情况下,重要的是要理解实时内存视图和堆遍历器中的数字通常会有很大的不同。 除了堆步行器显示的是与实时内存视图不同的时间点的快照外,它还排除了所有未引用的对象。 根据垃圾回收器的状态,未引用的对象可能会占据堆的很大一部分。