方法调用记录
记录方法调用对于分析器来说是最困难的任务之一,因为它是在相互矛盾的约束条件下进行的: 结果应该准确,完整, 其所产生的开销应该小到不会使得你从测量数据中得出的结论变得不准确。不幸的是,没有一种测量方式可以为所有类型的应用满足所有这些要求。 这就是为什么JProfiler要求你决定使用哪种方法。
采样和Instrumentation
测量方法调用可以通过两种完全不同的技术来完成,称为"采样"和"Instrumentation",这两种技术各有优缺点: 使用抽样,会定期检查线程的当前调用堆栈。使用instrumentation,会修改选定类的字节码以跟踪方法的进入和退出。 Instrumentation测量所有调用,可以获得所有方法的调用计数。
在处理采样数据时,整个采样周期(通常为5毫秒)取决于被采样调用堆栈。 通过大量的采样,会出现统计学上正确的画面。 采样的优点是它的开销很低,因为它的采集频率不高。无需修改字节码,而且采样周期远大于方法调用的常规持续时间。 缺点是无法确定任何方法调用次数。此外,被调用次数很少且运行时间很短的方法可能根本不会显示。 如果你正在寻找性能瓶颈,这无关紧要,但如果你试图了解你的代码的详细运行时特征,这可能是不方便的。
另一方面,如果许多运行时间很短的方法被Instrumentation,那么Instrumentation会带来很大的开销。 由于时间测量的内在开销,这种Instrumentation会扭曲性能热点的相对重要性, 但也因为许多本来会被热点编译器内联的方法现在必须保持独立的方法调用。对于耗时较长的方法调用,这种开销就无关紧要了。 如果你能找到一组好的主要执行高级操作的类,Instrumentation增加的开销很低,可能比采样更好。 JProfiler的开销热点检测也可以在一些运行后改善这种情况。 此外,调用次数往往是重要的信息,可以更容易地了解发生了什么。
全采样与异步采样
JProfiler为采样提供了两种不同的技术方案:"全采样"是通过一个单独的线程,定期暂停JVM中的所有线程,并检查其堆栈跟踪。 然而,JVM只在某些"安全点"暂停线程,从而引入了偏差。如果你有重度多线程的计算密集型(CPU-bound)代码,分析的热点分布可能会有偏斜。 另一方面,如果代码也执行大量的I/O密集型代码,这种偏差一般不会是一个问题。
对于重度计算密集型(CPU-bound)代码,为了帮助获得准确数字,JProfiler还提供了异步采样。 通过异步采样,在运行的线程本身上调用一个分析信号处理程序。然后分析代理检查本地堆栈并提取Java栈帧。 主要的好处是,这种采样方法没有安全点偏差,而且对于重度多线程计算密集型(CPU-bound)的应用程序来说,开销更低。 但是,对于CPU视图来说,只能观察到"运行(Running)"线程状态,而"等待(Waiting)"、"阻塞(Blocking)"或"网络I/O"线程状态则不能用这种方式测量。 探针数据总是通过字节码指令插入的方式收集,所以对于JDBC和类似数据你仍然可以观察到所有线程状态。
异步采样有跟踪截断的问题,只有调用堆栈的结尾可用。这就是为什么,对于异步采样,调用树没有热点视图来得有用。 异步采样只支持Linux和macOS。
从Java17开始,在Hotspot JVM上采样,JProfiler可以避免使用全局安全点,并且进行全采样的开销接近于零。 和异步采样相比,对于单个线程,它依然会引入一些安全点偏差,但是对于JVM中的所有线程不再有全局安全点的开销。 考虑到异步采样的缺点,对于Java17+,推荐使用全采样。
选择方法调用记录类型
使用哪种方法调用记录类型进行分析是一个重要的决定,没有哪种正确选择是适合所有情况的,所以你需要自己做出一个合理的选择。 当你创建一个新的会话时,会话启动对话框会询问你要使用哪种方法调用记录类型。 在以后任何时候,你都可以随时在会话设置对话框中更改方法调用记录类型。
作为一个简单指导,考虑以下问题,测试你的应用程序是否属于下面两种情况之一:
被分析应用程序属于I/O密集型?
这种情况是针对大部分时间花费在等待REST服务和JDBC数据库调用的许多Web应用程序。 对于这种情形,只要你小心选择你的调用树过滤器只包含你自己的代码,Instrumentation 将是最佳选择。被分析应用程序属于重度多线程和计算密集型(CPU-bound)?
这种情况针对,例如,编译器、图像处理应用程序或运行负载测试的Web服务器。 如果你在Linux或macOS上进行分析,在这种情况下,你应该选择异步采样以获得最准确的CPU时间。
否则,"全采样"一般是最合适的选项,建议作为新会话的默认选项。
本地取样
因为异步采样可以访问本地堆栈,所以也可以进行本地采样,默认情况下,不启用本地采样,因为它会在调用树中引入大量节点, 并将热点计算的重点转移到了本地代码中。如果你在本地代码中确实存在性能问题,你可以选择异步采样,并在会话设置中启用本地采样。
JProfiler解析属于每个本地堆栈框架的库的路径,在调用树中的本地方法节点上,JProfiler会在开头方括号中显示本地库的文件名。
关于聚合级别,本地库就像类,所以在"类"聚合级别中,同一本地库中所有后续的调用都将被聚合到一个节点中。 在"包"聚合级别中,所有后续的本地方法调用都会被聚合到一个节点中,而不考虑本地库。
要排除选定的本地库,你可以从该本地库中移除一个节点,并选择移除整个类。