CPU Profiling
当 JProfiler 测量方法调用的执行时间及其调用栈时,我们称之为“CPU Profiling”。这些数据以多种方式呈现。根据您要解决的问题,某种展示方式可能会更有帮助。默认情况下,CPU 数据不会被记录,您必须 开启 CPU 记录 以捕获有趣的用例。
调用树
跟踪所有方法调用及其调用栈会消耗大量内存,并且只能维持很短的时间,直到所有内存耗尽。此外,直观地理解繁忙 JVM 中的方法调用数量也不容易。通常,这个数字大到无法定位和跟踪踪迹。
另一个方面是,许多性能问题只有在数据被聚合后才会变得清晰。通过这种方式,您可以判断在特定时间段内方法调用相对于整个活动的重要性。单个踪迹中,您无法了解所查看数据的相对重要性。
这就是为什么 JProfiler 构建了一个累积的调用栈树,并注释了观察到的时间和调用次数。时间顺序被消除,只保留总数。树中的每个节点代表至少观察到一次的调用栈。节点有子节点,代表在该调用栈中看到的所有外部调用。
调用树是“CPU 视图”部分中的第一个视图,当您开始 CPU Profiling 时,这是一个很好的起点,因为从起始点到最细粒度终点的方法调用的自上而下视图最容易理解。JProfiler 按总时间对子节点进行排序,因此您可以深度优先打开树以分析对性能影响最大的部分。
虽然所有测量都是针对方法进行的,JProfiler 允许您通过在类或包级别上聚合调用树来获得更广泛的视角。聚合级别选择器还包含一个“JEE/Spring 组件”模式。如果您的应用程序使用 JEE 或 Spring,您可以使用此模式在类级别上仅查看 JEE 和 Spring 组件。像 URL 这样的拆分节点在所有聚合级别中都保留。
调用树过滤器
如果调用树中显示了所有类的方法,树通常太深而无法管理。如果您的应用程序由框架调用,调用树的顶部将由您不关心的框架类组成,而您自己的类将深埋其中。调用库将显示其内部结构,可能有数百个您不熟悉且无法影响的方法调用级别。
解决此问题的方法是对调用树应用过滤器,以便仅记录某些类。作为一个积极的副作用,收集的数据更少,必须被检测的类更少,因此开销减少。
默认情况下,分析会话配置了一个排除常用框架和库包的列表。
当然,这个列表是不完整的,所以最好删除它并自己定义感兴趣的包。事实上,检测和默认过滤器的组合是如此不理想,以至于 JProfiler 建议在会话启动对话框中更改它。
过滤器表达式与完全限定类名进行比较,因此 com.mycorp.
匹配所有嵌套包中的类,例如
com.mycorp.myapp.Application
。有三种类型的过滤器,称为“被分析”、“紧凑”和“忽略”。“被分析”类中的所有方法都被测量。这是您自己的代码所需的。
在“紧凑”过滤器包含的类中,仅测量对该类的第一次调用,但不显示进一步的内部调用。“紧凑”是您对库(包括 JRE)所需要的。例如,当调用 hashMap.put(a, b)
时,您可能希望在调用树中看到
HashMap.put()
,但不希望看到更多——除非您是映射实现的开发者,否则其内部工作应被视为不透明。
最后,“忽略”方法根本不被分析。由于开销考虑,它们可能不适合检测,或者它们可能在调用树中只是分散注意力,例如动态调用之间插入的内部 Groovy 方法。
手动输入包容易出错,因此您可以使用包浏览器。在您启动会话之前,包浏览器只能显示配置类路径中的包,这通常不包括所有实际加载的类。在运行时,包浏览器将显示所有已加载的类。
配置的过滤器列表从上到下对每个类进行评估。在每个阶段,如果有匹配项,当前过滤器类型可能会更改。重要的是过滤器列表的起始类型。如果您以“被分析”过滤器开始,类的初始过滤器类型是“紧凑”,这意味着只有显式匹配项被分析。
如果您以“紧凑”过滤器开始,类的初始过滤器类型是“被分析”。在这种情况下,除显式排除的类外,所有类都被分析。
调用树时间
为了正确解释调用树,理解显示在调用树节点上的数字非常重要。对于任何节点,有两个时间是有趣的,总时间和自耗时。自耗时是节点的总时间减去嵌套节点的总时间。
通常,自耗时很小,除了紧凑过滤的类。大多数情况下,紧凑过滤的类是叶节点,总时间等于自耗时,因为没有子节点。有时,紧凑过滤的类会调用被分析的类,例如通过回调或因为它是调用树的入口点,如当前线程的 run
方法。在这种情况下,一些未分析的方法消耗了时间,但在调用树中未显示。该时间会冒泡到调用树中第一个可用的祖先节点,并贡献给紧凑过滤类的自耗时。
调用树中的百分比条显示总时间,但自耗时部分以不同颜色显示。除非同一级别的两个方法被重载,否则方法显示时不带签名。在视图设置对话框中,有多种方法可以自定义调用树节点的显示。例如,您可能希望将自耗时或平均时间显示为文本,始终显示方法签名或更改使用的时间尺度。此外,百分比计算可以基于父时间而不是整个调用树的时间。
线程状态
在调用树的顶部,有几个视图参数可以更改显示的分析数据的类型和范围。默认情况下,所有线程都是累积的。JProfiler 按线程维护 CPU 数据,您可以显示单个线程或线程组。
在任何时候,每个线程都有一个关联的线程状态。如果线程准备好处理字节码指令或当前正在 CPU 核心上执行它们,则线程状态称为“Runnable”。在寻找性能瓶颈时,该线程状态是感兴趣的,因此默认选择它。
或者,线程可能正在等待监视器,例如,通过调用 Object.wait()
或
Thread.sleep()
,在这种情况下,线程状态称为“Waiting”。尝试获取监视器时被阻塞的线程,例如在 synchronized
代码块的边界处,则处于“Blocking”状态。
最后,JProfiler 添加了一个合成的“Net I/O”状态,用于跟踪线程等待网络数据的时间。这对于分析服务器和数据库驱动程序很重要,因为该时间可能与性能分析相关,例如调查慢速 SQL 查询。
如果您对挂钟时间感兴趣,您必须选择线程状态“所有状态”并选择单个线程。只有这样,您才能将时间与您在代码中通过调用 System.currentTimeMillis()
计算的持续时间进行比较。
如果您想将选定的方法移到不同的线程状态,可以使用方法触发器和“覆盖线程状态”触发器操作,或者使用 ThreadStatus
类在 嵌入式
或 注入式 探针 API 中。
在调用树中查找节点
有两种方法可以在调用树中搜索文本。首先,有快速搜索选项,可以通过从菜单中调用 视图→查找 或直接开始在调用树中键入来激活。匹配项将被高亮显示,按下
PageDown
后可以使用搜索选项。使用 ArrowUp
和 ArrowDown
键可以循环浏览不同的匹配项。
另一种搜索方法、类或包的方法是使用调用树底部的视图过滤器。在这里,您可以输入以逗号分隔的过滤器表达式列表。以“-”开头的过滤器表达式类似于忽略过滤器。以“!”开头的表达式类似于紧凑过滤器。所有其他表达式类似于被分析过滤器。就像过滤器设置一样,初始过滤器类型决定类是默认包含还是排除。
单击视图设置文本字段左侧的图标会显示视图过滤器选项。默认情况下,匹配模式是“包含”,但在搜索特定包时,“以...开头”可能更合适。
火焰图
查看调用树的另一种方法是作为火焰图。您可以通过调用关联的 调用树分析,将整个调用树或其一部分显示为火焰图。
火焰图在一张图像中显示调用树的全部内容。调用从火焰图的底部开始,并向顶部传播。每个节点的子节点排列在其正上方的行中。子节点按字母顺序排序,并以其父节点为中心。由于每个节点中花费的自耗时,“火焰”向顶部逐渐变窄。有关节点的更多信息显示在工具提示中,您可以在其中标记文本以将其复制到剪贴板。
如果鼠标光标附近的工具提示干扰了您的分析,您可以使用其右上角的按钮锁定它,然后使用工具提示顶部的握柄将其移动到方便的位置。相同的按钮或双击火焰图关闭工具提示。
火焰图的信息密度非常高,因此可能需要通过关注选定节点及其后代节点的层次结构来缩小显示的内容。虽然您可以放大感兴趣的区域,但也可以通过双击或使用上下文菜单设置新的根节点。当连续多次更改根节点时,您可以在根节点历史中再次向后移动。
分析火焰图的另一种方法是根据类名、包名或任意搜索词添加着色。可以从上下文菜单中添加着色,并可以在着色对话框中进行管理。对于每个节点,使用第一个匹配的着色。着色在分析会话之间持久化,并在所有会话和快照中全局使用。
除了着色,您还可以使用快速搜索功能查找感兴趣的节点。使用光标键可以在匹配结果之间循环,同时为当前高亮显示的匹配项显示工具提示。
热点
如果您的应用程序运行得太慢,您会想找到占用大部分时间的方法。使用调用树,有时可以直接找到这些方法,但通常这不起作用,因为调用树可能很宽,叶节点数量巨大。
在这种情况下,您需要调用树的逆:按总自耗时排序的所有方法列表,从所有不同的调用栈累积,并显示方法的调用方式。在热点树中,叶子是入口点,如应用程序的 main
方法或线程的
run
方法。从热点树中最深的节点,调用向上传播到顶级节点。
回溯中的调用次数和执行时间不指代方法节点,而是指沿此路径调用顶级热点节点的次数。这一点很重要:乍一看,您可能会期望节点上的信息量化对该节点的调用。然而,在热点树中,该信息显示了节点对顶级节点的贡献。因此,您必须这样阅读数字:沿着这个反向调用栈,顶级热点被调用了
n
次,总持续时间为 t
秒。
默认情况下,热点是根据自耗时计算的。您也可以根据总时间计算它们。这对于分析性能瓶颈不是很有用,但如果您想查看所有方法的列表可能会很有趣。热点视图仅显示最大数量的方法以减少开销,因此您要查找的方法可能根本不显示。在这种情况下,使用底部的视图过滤器过滤包或类。与调用树相反,热点视图过滤器仅过滤顶级节点。热点视图中的截止点不是全局应用的,而是相对于显示的类应用的,因此应用过滤器后可能会出现新节点。
热点和过滤器
热点的概念不是绝对的,而是取决于调用树过滤器。如果您没有任何调用树过滤器,最大的热点很可能总是 JRE 核心类中的方法,如字符串操作、I/O 例程或集合操作。这样的热点不会很有用,因为您通常无法直接控制这些方法的调用,也无法加速它们。
为了对您有用,热点必须是您自己类中的方法或您直接调用的库类中的方法。在调用树过滤器方面,您自己的类在“被分析”过滤器中,库类在“紧凑”过滤器中。
在解决性能问题时,您可能希望消除库层,只查看您自己的类。您可以通过在热点选项弹出窗口中选择 添加到调用的被分析类 单选按钮快速切换到该视角。
调用图
在调用树和热点视图中,每个节点可能会多次出现,尤其是在递归调用时。在某些情况下,您对方法中心的统计感兴趣,其中每个方法只出现一次,所有传入和传出调用都可见。这样的视图最好显示为图形,在 JProfiler 中,它被称为调用图。
图形的一个缺点是其视觉密度低于树。这就是为什么 JProfiler 默认缩写包名并默认隐藏总时间少于 1% 的传出调用。只要节点有传出扩展图标,您可以再次单击它以显示所有调用。在视图设置中,您可以配置此阈值并关闭包缩写。
扩展调用图时,它可能会很快变得混乱,尤其是如果您多次回溯。使用撤销功能恢复图形的先前状态。就像调用树一样,调用图提供快速搜索。通过在图形中键入,您可以开始搜索。
图形和树视图各有优缺点,因此有时您可能希望从一种视图类型切换到另一种。在交互式会话中,调用树和热点视图显示实时数据并定期更新。然而,调用图是在请求时计算的,并且在您扩展节点时不会更改。调用树中的 在调用图中显示 操作计算新的调用图并显示选定的方法。
从图形切换到调用树是不可能的,因为数据通常在稍后时间不再可比。然而,调用图提供调用树分析,其 视图→分析 操作可以为您显示累积传出调用和每个选定节点的回溯树。
超越基础
调用树、热点视图和调用图的组合具有许多高级功能,这些功能在 不同章节 中详细解释。此外,还有其他高级 CPU 视图在 单独 介绍。