JProfiler帮助文档

CPU分析


当JProfiler测量方法调用的执行时间和它们的调用堆栈时,我们称之为"CPU分析"。 这些数据以多种方式呈现。根据你试图解决的问题,一种或另一种展示方式将是最有帮助的。 默认不记录CPU数据,你必须打开CPU记录才能采集到有趣的用例。

调用树

跟踪所有的方法调用及其调用栈会消耗相当大的内存,短时间内就会耗尽所有内存。 另外,在一个繁忙的JVM中,很难直观获得方法调用的数量。通常情况下,这个数字是如此之大,以至于定位和跟随跟踪是不可能的。

另一个方面,只有将收集到的数据进行汇总,许多性能问题才会变得清晰。 这样,你就可以知道在某个时间段内,方法调用相对于整个活动的重要性。 如果是单一的跟踪,你对你所看的数据的相对重要性没有概念。

这就是为什么JProfiler建立了一个所有观察到的调用堆栈的累积树,并注解有观察到的时间和调用次数。 时间顺序信息被消除,只保留总数。树中的每个节点代表一个至少被观察过一次的调用堆栈。 节点的子节点代表在该调用堆栈中看到的所有传出调用。

A B  A B C    A B DA: 7 msB: 6 msC: 3 msD: 1 msA C  2 ms1 ms3 ms1 msC: 1 ms调用堆栈的方法调用调用树

调用树是"CPU视图"部分的第一个视图,当你开始进行CPU分析时,它是一个很好的起点, 因为遵循方法调用从起点到最细化的终点的自上而下视图,最容易理解。 JProfiler按照子节点的总时间进行排序,所以你可以深度优先打开树,分析对性能影响最大的部分。

虽然所有的测量都是针对方法进行的,但JProfiler允许你通过在类或包级别上聚合调用树来获得更广阔的视角。 聚合级别选择器还包含一个"Java EE组件"模式。如果你的应用程序使用Java EE,你可以使用该模式只查看类级别上的JEE组件。 像URL这样的拆分节点会在所有聚合级别中保留。

调用树过滤器

如果所有类的方法都显示在调用树中,那么这棵树通常太深而无法管理。 如果你的应用程序被框架调用, 调用树的顶部将由你不关心的框架类组成,而你自己的类将被深埋。对库的调用会显示它们的内部结构, 可能会有上百层级的方法调用,而这些方法你并不熟悉,也无法影响。

这个问题的解决方案是对调用树应用过滤器,这样只会记录一些类。 作为一个积极的副作用,需要收集的数据更少,需要检测的类也更少,因此开销也就减少了。

默认情况下,分析会话配置了一个常用框架和库的排除包列表。

当然这个列表是不完整的,所以你最好删除它,定义自己感兴趣的包。 事实上,instrumentation和默认过滤器的组合非常不可取, 所以JProfiler建议在会话启动对话框中更改。

过滤器表达式与完全限定类名进行比对,所以com.mycorp.,匹配所有嵌套包中的类, 比如com.mycorp.myapp.Application 。 有三种类型的过滤器,分别称为"profiled(被分析)"、"compact(压缩)"和"ignored(被忽略)"。 所有"profiled(被分析)"类中的方法都会被测量。这是你自己的代码所需要的。

在一个被"compact(压缩)"过滤器所包含的类中,只测量对该类的第一次调用,但不显示进一步的内部调用。 "compact(压缩)"是你想要针对库使用的,包括JRE。例如,当调用hashMap.put(a, b) 时, 你可能希望在调用树中看到HashMap.put() ,但仅限于此-它的内部工作原理应该被视为不透明的, 除非你是map实现的开发者。

最后,"ignored(被忽略)"的方法根本不会被分析。出于开销的考虑,可能不需要它们, 或者它们可能只是在调用树中分散注意力,比如在动态调用之间插入的内部Groovy方法。

手动输入包容易出错,所以你可以使用包浏览器。在开始会话之前,包浏览器只能向你显示配置的类路径中的包, 这往往不能覆盖所有实际加载的类。在运行时,包浏览器将向你显示所有加载的类。

配置的过滤器列表从上到下对每个类别进行评估。在每个阶段,如果有匹配,当前的过滤器类型可能会改变。 过滤器列表的开始是什么过滤器开始很重要。如果你以"profiled(被分析)"过滤器开始, 一个类的初始过滤器类型是"compact(压缩)",这意味着只有明确匹配才会被分析。

a.*a.b.*a.b.c.*a.Aa.b.Ba.b.c.C默认:123结果:d.D被分析压缩匹配

如果你"compact(压缩)"过滤器开始,类的初始过滤器类型是"profiled(被分析)"。 在这种情况下,除了明确排除的类之外,所有的类都会被分析。

a.*a.b.*a.b.c.*a.Aa.b.Ba.b.c.C默认:123结果:d.D被分析压缩匹配

调用树时间

要正确解读调用树,了解调用树节点上显示的数字很重要。任何节点都有两个时间值得关注,总时间和自身时间, 自身时间是节点的总时间减去嵌套节点中的总时间。

通常情况下,自身时间是很小的,除了compact(压缩)过滤类。大多数情况下,一个compact(压缩)过滤的类是一个叶子节点, 由于没有子节点,所以总时间等于自身时间。有时,一个compact(压缩)过滤的类会调用一个profiled(被分析)类, 例如通过回调或者因为它是调用树的入口点,比如当前线程的run 方法。 在这种情况下,一些未分析的方法消耗了时间,但没有显示在调用树中。这些时间会冒泡到调用树中第一个可用的祖先节点,并贡献给compact(压缩)过滤类的自身时间。

A: 自身时间 1 msC: 自身时间 3 msB: 自身时间 2 ms被分析压缩B: 自身时间 6 msX: 自身时间 3 msY: 自身时间 1 ms实际调用序列过滤后的调用序列

调用树中的百分比条显示的是总时间,但自身时间部分用不同的颜色显示。 除非同一层级上的两个方法重载,否则不显示方法的签名。在视图设置对话框中,有多种方法可以自定义调用树节点的显示。 例如,你可能希望将自身时间或平均时间显示为文本,始终显示方法签名或更改使用的时间刻度。 此外,百分比计算也可以基于父节点时间而不是整个调用树的时间。

线程状态

在调用树的顶部,有几个视图参数可以改变显示的分析数据的类型和范围。默认情况下,会累积所有线程。 JProfiler以每个线程为基础维护CPU数据,你可以显示单个线程或线程组。

在任何时候,每个线程都有一个相应的线程状态。如果线程已经准备好处理字节码指令,或者当前正在CPU处理器上执行这些指令, 则线程状态称为"就绪(Runnable)"。 在寻找性能瓶颈时,该线程状态是值得关注的,所以默认选择它。

另外,一个线程可能正在等待一个Monitor,例如通过调用Object.wait()Thread.sleep() , 在这种情况下,该线程状态称为"等待(Waiting)"。一个线程在试图获取Monitor时被阻塞, 例如在synchronized 代码块的边界处,则处于"阻塞(Blocking)"状态。

最后,JProfiler增加了一个合成的"Net I/O"状态,用于跟踪线程等待网络数据的时间。 这对于分析服务器和数据库驱动程序很重要,因为该时间可能与性能分析有关,例如调查缓慢的SQL查询。

如果你对挂钟时间感兴趣,你必须选择线程状态"所有状态",同时选择一个线程。 只有这样,你才能将时间与你在代码中调用System.currentTimeMillis() 计算出的持续时间进行比较。

如果你想将选定的方法转移到不同的线程状态,你可以通过一个方法触发器和"覆盖线程状态"触发动作来实现, 或者通过使用嵌入式注入式探针API中的 ThreadStatus 类来实现。

在调用树中查找节点

有两种方式可以在调用树中搜索文本。首先,通过调用菜单 视图->查找会激活一个快速搜索选项, 或直接开始在调用树中输入。按PageDown 键后,匹配项将被高亮显示,并提供搜索选项。 通过上箭头下箭头 键,你可以循环查看不同的匹配项。

搜索方法、类或包的另一种方法是使用调用树底部的视图过滤器。在这里你可以输入一个以逗号分隔的过滤表达式列表。 以"-"开头的过滤器表达式类似于被忽略(Ignored)过滤器。以"!"开头的表达类似于压缩(Compact)过滤器。 所有其他的表达式类似于被分析(Profiled)过滤器。就像过滤器设置一样,初始过滤器类型决定了默认情况下是包含还是排除类。

单击视图设置文本字段左侧的图标,会显示视图过滤器选项。默认情况下,匹配模式为"包含",但搜索特定包时, "头部匹配"可能更合适。

火焰图

另一种查看调用树的方式是火焰图。你可以通过调用相应的调用树分析 将整个或部分调用树显示为火焰图。

火焰图在一张图像中显示了调用树的全部内容。调用从火焰图的底部发起,向顶部传递。 每个节点的子节点排列在其正上方的行中。子节点按字母顺序排列,并以其父节点为中心。 由于在每个节点上花费的自身时间,"火焰"向顶部逐渐变窄。关于节点的更多信息显示在工具提示中, 你可以标记文本将其复制到剪贴板。

火焰图具有非常高的信息密度,因此可能需要通过关注选定的节点及其子节点的层次结构来缩小显示的内容。 尽管你可以放大感兴趣的区域,但你也可以通过双击它或使用上下文菜单来设置一个新的根节点。 当连续多次更改根节点时,你可以在根节点历史中再次回退。

分析火焰图的另一种方法是根据类名、包名或任意搜索词添加着色。着色可以从上下文菜单中添加,也可以在着色对话框中进行管理。 每个节点都会使用第一个匹配的着色。除了着色之外,你还可以使用快速搜索功能来查找感兴趣的节点。

热点

如果你的应用程序运行得太慢,你要找到那些占用大部分时间的方法。通过调用树,有时可以直接找到这些方法, 但通常这样做是行不通的,因为调用树可能很大而且有大量叶节点

在这种情况下,你需要反转调用树:一个所有方法的列表,按其总的自身时间排序,从所有不同的调用堆栈中累计出来, 并通过回溯跟踪显示这些方法是如何被调用的。 在热点树中,叶节点是入口点, 就像应用程序的main 方法或线程的run 方法。 从热点树中最深的节点开始,调用向上传递到顶层节点。

回溯跟踪中的调用次数和执行时间并不是指该方法节点,而是指顶层热点节点在这条路径上被调用的次数。 理解这一点很重要:粗略一看,你会认为看到的节点上的信息是该节点的调用次数。 然而,在热点树中,该信息显示的是该节点对顶层节点的贡献。 所以,你必须这样理解这些数字: 沿着这个倒置的调用堆栈,顶层热点被调用了n 次,总持续时间为t 秒。

方法 A计数 5方法 C计数 3方法 C计数 1方法 B计数 2调用树热点Method C计数 4方法 A计数 3方法 B计数 1回溯跟踪热点调用计数倒转

默认情况下,热点是根据自身时间计算出来的,你也可以根据总时间计算。这对于分析性能瓶颈不是很有用, 但如果你想查看所有方法的列表,会有用。热点视图只显示有限数量的方法,以减少开销,所以你正在寻找的方法可能根本不显示。 在这种情况下,使用底部的视图过滤器来过滤包或类。与调用树相反,热点视图过滤器只过滤顶层节点。 热点视图中的截断不是全局应用,而是针对显示的类,因此应用过滤器后可能会出现新的节点。

热点和过滤器

热点的概念并不是绝对的,而是取决于调用树过滤器,如果你完全没有调用树过滤器,最大的热点很可能永远是JRE核心类中的方法, 比如字符串操作、I/O例程或者集合操作。这样的热点不会很有用,因为你往往不能直接控制这些方法的调用, 也没有办法提升它们的速度。

热点必须是你自己的类中的方法或者是你直接调用的库类中的方法,才对你有用。 从调用树过滤器的角度,你自己的类在"被分析(profiled)"过滤器中,库类在"压缩(compact)"过滤器中。

在解决性能问题时,你可能希望排除库层,只看你自己的类。 你可以通过在热点选项弹出菜单中选择添加到调用分析类单选按钮,在调用树中快速切换到该视角。

调用图

无论是调用树中,还是在热点视图中,每个节点都可能出现多次,尤其是递归调用的情况。 在某些情况下,你对以方法为中心的统计感兴趣,在这种情况下,每个方法只出现一次,所有的传入和传出调用都是可见的。 这样的视图最好以图的形式显示,在JProfiler中,它被称为调用图。

图的一个缺点是其视觉密度比树低。这也是为什么JProfiler默认会缩写包名,并默认隐藏小于总时间1%的传出调用, 只要节点有传出调用展开图标,就可以重复点击它以显示所有调用,在视图设置中,你可以配置这个阈值,并关闭包缩写。

当展开调用图时,可能很快就会变得混乱,尤其是当你多次返回时。使用撤销功能来恢复图的之前状态。 就像调用树一样,调用图提供了快速搜索功能。通过在图中输入,你可以开始搜索。

图和树视图各有优缺点,因此你有时可能希望从一种视图类型切换到另一种。 在交互式会话中调用树和热点视图显示实时数据并定期更新。然而调用图是根据需求计算的, 当你展开节点不会改变。调用树中的在调用图中显示操作会计算一个新的调用图并显示所选方法。

从图切换到调用树是不可能的,因为数据在过一段时间后通常不再具有可比性。 然而,调用图通过它的 视图->分析操作提供了调用树分析, 可以为你显示每个选定节点的累积传出调用树和回溯跟踪树。

超越基础

调用树、热点视图和调用图都有许多高级功能,将在其他章节中详细说明。 另外,还有其他的高级CPU视图也会单独介绍。