当 JProfiler 测量方法调用的执行时间及其调用栈时,我们称之为 “CPU 分析 (CPU profiling)”。这些数据会以多种方式展示。根据你要解决的问题,不同的展示方式会更有帮助。CPU 数据默认不会被记录,你需要开启 CPU 记录来捕获感兴趣的用例。
调用树 (Call tree)
跟踪所有方法调用及其调用栈会消耗大量内存,并且只能在内存耗尽前维持很短的时间。此外,在繁忙的 JVM 中,方法调用的数量很难直观把握。通常,这个数量非常大,导致定位和跟踪变得不可能。
另一个方面是,许多性能问题只有在对收集到的数据进行聚合后才会显现。通过这种方式,你可以了解某些方法调用在特定时间段内对整体活动的重要性。单独的调用跟踪无法反映你正在查看的数据的相对重要性。
这就是 JProfiler 构建所有观察到的调用栈的累积树的原因,并用观察到的时间和调用次数进行标注。时间顺序被消除,只保留总数。树中的每个节点代表至少被观察到一次的调用栈。节点有子节点,表示在该调用栈上观察到的所有外部调用。
调用树是“CPU 视图 (CPU views)”部分的第一个视图,也是你开始 CPU 分析时的良好起点,因为自顶向下的视图可以从起始点跟踪方法调用到最细粒度的终点,最容易理解。JProfiler 会根据总耗时对子节点排序,因此你可以深度优先展开树,分析对性能影响最大的部分。
虽然所有测量都是针对方法进行的,JProfiler 允许你通过在类或包级别聚合调用树来获得更广泛的视角。聚合级别 (aggregation level) 选择器还包含“JEE/Spring 组件”模式。如果你的应用使用了 JEE 或 Spring,可以用此模式仅在类级别查看 JEE 和 Spring 组件。像 URL 这样的拆分节点 (splitting nodes) 在所有聚合级别中都会保留。
调用树过滤器 (Call tree filters)
如果调用树中显示了所有类的方法,树通常会太深而难以管理。如果你的应用由框架调用,调用树的顶部将由你不关心的框架类组成,而你自己的类会被深深埋藏。对库的调用会显示其内部结构,可能有数百层你不熟悉且无法影响的方法调用。
解决此问题的方法是对调用树应用过滤器,只记录部分类。这样做的一个积极副作用是需要收集的数据更少,需要插桩的类也更少,从而降低了开销。
默认情况下,分析会话 (profiling session) 配置了常用框架和库的排除包列表。
当然,这个列表并不完整,因此最好删除它并自行定义感兴趣的包。实际上,插桩 (instrumentation)与默认过滤器的组合非常不理想,JProfiler 会在会话启动对话框中建议你进行更改。
过滤表达式会与完全限定类名进行比较,因此com.mycorp.会匹配所有嵌套包中的类,例如com.mycorp.myapp.Application。
过滤器有三种类型,分别为“被分析 (profiled)”、“紧凑 (compact)”和“忽略 (ignored)”。所有“被分析 (profiled)”类中的方法都会被测量。这正是你需要分析自己代码时的设置。
在被“紧凑 (compact)”过滤器包含的类中,只会测量首次进入该类的调用,内部进一步的调用不会显示。“紧凑 (compact)”适用于库,包括 JRE。例如,调用hashMap.put(a, b)时,你可能只想在调用树中看到HashMap.put(),而不需要了解其内部实现——除非你是该
map 实现的开发者。
最后,“忽略 (ignored)”方法完全不会被分析 (profiled)。出于开销考虑,这些方法可能不适合插桩,或者它们在调用树中只是干扰项,比如动态调用之间插入的 Groovy 内部方法。
手动输入包名容易出错,因此你可以使用包浏览器。在启动会话前,包浏览器只能显示配置类路径 (classpath) 下的包,这通常无法覆盖所有实际加载的类。运行时,包浏览器会显示所有已加载的类。
配置的过滤器列表会针对每个类自上而下依次评估。在每一步,如果匹配,当前过滤器类型可能会发生变化。列表的起始过滤器类型很重要。如果你以“被分析 (profiled)”过滤器开头,类的初始过滤器类型为“紧凑 (compact)”,意味着只有明确匹配的类会被分析 (profiled)。
如果以“紧凑 (compact)”过滤器开头,类的初始过滤器类型为“被分析 (profiled)”。此时,除明确排除的类外,所有类都会被分析 (profiled)。
调用树时间 (Call tree times)
为了正确解读调用树,理解节点上显示的数字非常重要。每个节点有两个关键时间:总耗时 (total time) 和自耗时 (self-time)。自耗时 (self-time) 是节点的总耗时减去所有嵌套节点的总耗时。
通常,自耗时 (self-time) 很小,除了被紧凑过滤的类。大多数情况下,紧凑过滤的类是叶子节点,总耗时等于自耗时,因为没有子节点。有时,紧凑过滤的类会调用被分析 (profiled)
的类,例如通过回调或作为调用树的入口点(如当前线程的run方法)。此时,一些未分析 (unprofiled)
的方法消耗了时间,但不会显示在调用树中。这部分时间会向上传递到调用树中第一个可用的祖先节点,并计入紧凑过滤类的自耗时 (self-time)。
调用树中的百分比条显示总耗时 (total time),但自耗时 (self-time) 部分会用不同颜色显示。方法默认不显示签名,除非同一层级有重载方法。你可以在视图设置对话框中自定义调用树节点的显示方式。例如,可以选择显示自耗时 (self-time) 或平均时间为文本、始终显示方法签名或更改时间刻度。百分比计算也可以基于父节点时间,而不是整个调用树的时间。
线程状态 (Thread status)
在调用树顶部有多个视图参数,可以更改显示分析数据的类型和范围。默认情况下,所有线程都会被累积统计。JProfiler 会为每个线程维护 CPU 数据,你也可以选择显示单个线程或线程组。
每时每刻,每个线程都有一个关联的线程状态。如果线程已准备好处理字节码指令,或正在 CPU 核心上执行,则线程状态为“Runnable”。查找性能瓶颈时,这种线程状态最为重要,因此默认选中。
或者,线程可能正在等待 monitor,例如调用Object.wait()或Thread.sleep(),此时线程状态为“Waiting”。线程在尝试获取 monitor
时被阻塞(如在synchronized代码块边界),则处于“Blocking”状态。
最后,JProfiler 增加了一个合成的“Net I/O”状态,用于跟踪线程等待网络数据的时间。这对于分析服务器和数据库驱动非常重要,因为这段时间可能与性能分析相关,例如排查慢 SQL 查询。
如果你关注 wall-clock 时间,需要选择线程状态“All
states”,并且还要选择单个线程。只有这样,你才能将时间与代码中通过System.currentTimeMillis()计算的持续时间进行比较。
如果你想将选中的方法切换到不同的线程状态,可以通过方法触发器 (trigger) 和“Override thread status”触发器动作实现,或者在嵌入式 (embedded)或注入式 (injected)探针 (probe) API 中使用ThreadStatus类。
在调用树中查找节点 (Finding nodes in the call tree)
有两种方式可以在调用树中搜索文本。第一种是快速搜索 (quicksearch)
选项,通过菜单View→Find激活,或直接在调用树中输入内容。匹配项会被高亮显示,按下PageDown后可使用更多搜索选项。使用ArrowUp和ArrowDown键可以循环切换不同的匹配项。
另一种方式是在调用树底部使用视图过滤器 (view filter) 搜索方法、类或包。你可以输入用逗号分隔的过滤表达式。以“-”开头的表达式类似于忽略过滤器 (ignored filter),以“!”开头的表达式类似于紧凑过滤器 (compact filter),其他表达式类似于被分析过滤器 (profiled filter)。与过滤器设置类似,初始过滤器类型决定类默认是包含还是排除。
点击视图设置文本框左侧的图标可以显示视图过滤器选项。默认匹配模式为“包含 (Contains)”,但在搜索特定包时,“以...开头 (Starts with)”可能更合适。
火焰图 (Flame graphs)
查看调用树的另一种方式是火焰图 (flame graph)。你可以通过关联的调用树分析 (call tree analysis),将整个调用树或其部分显示为火焰图。
火焰图 (flame graph) 能将调用树的全部内容以一张图像展示。调用从火焰图底部开始,向上传播。每个节点的子节点排列在其正上方一行。子节点按字母顺序排序,并居中于父节点。由于每个节点的自耗时 (self-time),“火焰”会向上逐渐变窄。节点的更多信息会显示在工具提示 (tooltip) 中,你可以选中其中的文本复制到剪贴板。
如果鼠标附近的工具提示 (tooltip) 干扰你的分析,可以通过右上角的按钮锁定它,然后用顶部的拖动手柄将其移动到合适位置。再次点击该按钮或双击火焰图可关闭工具提示。
火焰图 (flame graph) 信息密度极高,因此有必要通过聚焦选中节点及其后代节点来缩小显示内容。你可以放大感兴趣的区域,也可以通过双击或右键菜单设置新的根节点。连续多次更换根节点时,可以在根节点历史中回退。
分析火焰图的另一种方式是根据类名、包名或任意搜索词添加着色 (colorization)。着色 (colorization) 可通过右键菜单添加,并在着色对话框中管理。每个节点使用第一个匹配的着色 (colorization)。着色 (colorization) 会在分析会话 (profiling session) 之间持久化,并全局应用于所有会话和快照。
除了着色 (colorization) 外,你还可以使用快速搜索功能查找感兴趣的节点。使用方向键可以在匹配结果间循环切换,同时工具提示会显示当前高亮的匹配项。
热点 (Hot spots)
如果你的应用运行缓慢,你会想找到最耗时的方法。通过调用树有时可以直接找到这些方法,但通常不行,因为调用树可能很宽,叶子节点数量巨大。
这时,你需要调用树的逆向视图:按总自耗时 (self-time) 排序的所有方法列表,汇总自所有不同的调用栈,并带有显示方法如何被调用的回溯 (backtrace)。在热点树 (hot spot tree)
中,叶子节点是入口点,如应用的main方法或线程的run方法。从热点树最深的节点,调用会向上传播到顶层节点。
回溯 (backtrace) 中的调用次数和执行时间并不是指方法节点本身,而是指沿该路径调用顶层热点节点的次数。这一点很重要:乍一看,你可能以为节点上的信息量化了对该节点的调用。但在热点树 (hot spot tree)
中,这些信息显示的是该节点对顶层节点的贡献。因此,你需要这样解读数字:沿着这个反向调用栈,顶层热点被调用了n次,总耗时t秒。
默认情况下,热点 (hot spot) 是根据自耗时 (self-time) 计算的。你也可以根据总耗时 (total time) 计算。这对于分析性能瓶颈并不太有用,但如果你想查看所有方法的列表会很方便。热点视图 (hot spot view) 只显示有限数量的方法以减少开销,因此你要找的方法可能不会显示。这种情况下,可以用底部的视图过滤器 (view filter) 过滤包或类。与调用树不同,热点视图过滤器 (hot spot view filter) 只过滤顶层节点。热点视图的截断不是全局应用的,而是针对显示的类,因此应用过滤器后可能会出现新节点。
热点与过滤器 (Hot spots and filters)
热点 (hot spot) 的概念不是绝对的,而是取决于调用树过滤器 (call tree filter)。如果你没有任何调用树过滤器,最大的热点很可能总是 JRE 核心类中的方法,如字符串操作、I/O 例程或集合操作。这些热点并不实用,因为你通常无法直接控制这些方法的调用,也无法加速它们。
对你有用的热点 (hot spot) 必须是你自己类中的方法,或你直接调用的库类方法。就调用树过滤器而言,你自己的类属于“被分析 (profiled)”过滤器,库类属于“紧凑 (compact)”过滤器。
解决性能问题时,你可能希望排除库层,只关注自己的类。你可以在调用树中快速切换到这种视角,只需在热点选项弹窗中选择添加到调用的被分析类 (Add to calling profiled class)单选按钮。
调用图 (Call graph)
无论是在调用树还是热点视图中,每个节点都可能出现多次,尤其是递归调用时。在某些情况下,你需要以方法为中心的统计信息,每个方法只出现一次,所有入站和出站调用都可见。这样的视图最好以图 (graph) 的形式展示,在 JProfiler 中,这被称为调用图 (call graph)。
图 (graph) 的一个缺点是其可视化密度低于树 (tree)。因此,JProfiler 默认会缩写包名,并隐藏总耗时低于 1% 的出站调用。只要节点有出站展开图标,你可以再次点击显示所有调用。在视图设置中,你可以配置该阈值并关闭包名缩写。
展开调用图 (call graph) 时,尤其是多次回溯时,图很快会变得混乱。可以使用撤销功能恢复图的先前状态。与调用树类似,调用图也支持快速搜索。只需在图中输入内容即可开始搜索。
图 (graph) 和树 (tree) 视图各有优缺点,因此你有时可能希望在不同视图类型之间切换。在交互式会话中,调用树和热点视图显示实时数据并定期更新。调用图 (call graph) 则是在请求时计算,展开节点时不会改变。调用树中的在调用图中显示 (Show in Call Graph)操作会计算新的调用图并显示选中的方法。
由于数据通常在之后已不可比,因此无法从图 (graph) 切换回调用树 (call tree)。不过,调用图 (call graph) 通过其View→Analyze操作提供调用树分析 (call tree analysis),可以为每个选中节点显示累积的出站调用树和回溯树。
进阶内容 (Beyond the basics)
调用树 (call tree)、热点视图 (hot spots view) 和调用图 (call graph) 组合拥有许多高级功能,详细内容请参见其他章节。此外,还有其他高级 CPU 视图,详见专门章节。





































