方法调用记录


记录方法调用对于分析器来说是最困难的任务之一,因为它是在相互矛盾的约束条件下进行的: 结果应该准确,完整, 其所产生的开销应该小到不会使得你从测量数据中得出的结论变得不准确。不幸的是,没有一种测量方式可以为所有类型的应用满足所有这些要求。 这就是为什么JProfiler要求你决定使用哪种方法。

采样和Instrumentation

测量方法调用可以通过两种完全不同的技术来完成,称为"采样"和"Instrumentation",这两种技术各有优缺点: 使用抽样,会定期检查线程的当前调用堆栈。使用instrumentation,会修改选定类的字节码以跟踪方法的进入和退出。 Instrumentation测量所有调用,可以获得所有方法的调用计数。

在处理采样数据时,整个采样周期(通常为5毫秒)取决于被采样调用堆栈。 通过大量的采样,会出现统计学上正确的画面。 采样的优点是它的开销很低,因为它的采集频率不高。无需修改字节码,而且采样周期远大于方法调用的常规持续时间。 缺点是无法确定任何方法调用次数。此外,被调用次数很少且运行时间很短的方法可能根本不会显示。 如果你正在寻找性能瓶颈,这无关紧要,但如果你试图了解你的代码的详细运行时特征,这可能是不方便的。

方法 A: +5 ms方法 B: +5 ms方法 X: +5 ms方法 A: +5 ms方法 B: +5 ms方法 Y: +5 ms时间TT + 5 ms

另一方面,如果许多运行时间很短的方法被Instrumentation,那么Instrumentation会带来很大的开销。 由于时间测量的内在开销,这种Instrumentation会扭曲性能热点的相对重要性, 但也因为许多本来会被热点编译器内联的方法现在必须保持独立的方法调用。对于耗时较长的方法调用,这种开销就无关紧要了。 如果你能找到一组好的主要执行高级操作的类,Instrumentation增加的开销很低,可能比采样更好。 JProfiler的开销热点检测也可以在一些运行后改善这种情况。 此外,调用次数往往是重要的信息,可以更容易地了解发生了什么。

方法 AX: 3.5 msY: 4.5 ms时间以 ms 分析代理进入进入进入退出退出退出123456789101112131415方法 B: 11 ms调用调用调用

全采样与异步采样

JProfiler为采样提供了两种不同的技术方案:"全采样"是通过一个单独的线程,定期暂停JVM中的所有线程,并检查其堆栈跟踪。 然而,JVM只在某些"安全点"暂停线程,从而引入了偏差。如果你有重度多线程的计算密集型(CPU-bound)代码,热点的分析分布可能会有很大的偏斜。 另一方面,如果代码也执行大量的I/O密集型代码,这种偏差一般不会是一个问题。

对于重度计算密集型(CPU-bound)代码为了帮助获得准确数字,JProfiler还提供了异步采样。 通过异步采样,在运行的线程本身上调用一个分析信号处理程序。然后分析代理检查本地堆栈并提取Java栈帧。 主要的好处是,这种采样方法没有安全点偏差,而且对于重度多线程计算密集型(CPU-bound)的应用程序来说,开销更低。 但是,对于CPU视图来说,只能观察到"运行(Running)"线程状态,而"等待(Waiting)"、"阻塞(Blocking)"或"网络I/O"线程状态则不能用这种方式测量。 探针数据总是通过字节码指令插入的方式收集,所以对于JDBC和类似数据你仍然可以观察到所有线程状态。

异步采样只在Linux和macOS上支持。不支持Windows,因为该操作系统不提供POSIX风格的信号处理程序。

时间采样线程线程 2线程 1安全点偏差全采样:异步采样:线程 2线程 1TT + 5 ms

选择方法调用记录类型

使用哪种方法调用记录类型进行分析是一个重要的决定,没有哪种正确选择是适合所有情况的,所以你需要自己做出一个合理的选择。 当你创建一个新的会话时,会话启动对话框会询问你要使用哪种方法调用记录类型。 在以后任何时候,你都可以随时在会话设置对话框中更改方法调用记录类型。

作为一个简单指导,考虑以下问题,测试你的应用程序是否属于下面两种情况之一:

否则,"全采样"一般是最合适的选项,建议作为新会话的默认选项。

本地取样

因为异步采样可以访问本地堆栈,所以也可以进行本地采样,默认情况下,不启用本地采样,因为它会在调用树中引入大量节点, 并将热点计算的重点转移到了本地代码中。如果你在本地代码中确实存在性能问题,你可以选择异步采样,并在会话设置中启用本地采样。

JProfiler解析属于每个本地堆栈框架的库的路径,在调用树中的本地方法节点上,JProfiler会在开头方括号中显示本地库的文件名。

关于聚合级别,本地库就像类,所以在"类"聚合级别中,同一本地库中所有后续的调用都将被聚合到一个节点中。 在"包"聚合级别中,所有后续的本地方法调用都会被聚合到一个节点中,而不考虑本地库。

要排除选定的本地库,你可以从该本地库中移除一个节点,并选择移除整个类。