小程序使用async
免责声明:该故事并非由Async Profiler赞助。
总览
大多数Java采样分析器都依靠“ Java虚拟机工具接口(JVM TI) ”来分析Java应用程序。 但是,JVM TI有一个固有的局限性。 JVM TI仅允许在安全点收集堆栈跟踪。 因此,任何使用JVM TI的采样分析器都存在安全点偏差问题。
“ Async Profiler ”不使用JVMTI来获取堆栈跟踪样本,因此避免了安全点偏差问题。 让我们首先了解这个问题。
那么,什么是安全点?
“安全点是时刻,线程的数据,内部状态和在JVM中的表示很安全,可以被JVM中的其他线程观察。”
以下是Java应用程序中安全点的一些示例。
- 每2个字节码之间(解释器模式)
- 非计数循环的后端
- 方法退出
- JNI呼叫退出
安全点偏差问题
Nitsan Wakart在许多地方都谈到了这个安全点偏差问题。 他的博客文章“ 为什么(大多数)采样Java Profiler如此糟糕 ”,可能是描述此问题的最全面的博客文章。
总而言之,当采样探查器获得样本堆栈跟踪时,应用程序线程将停止以收集数据,并且线程将恢复。 这里的问题是,应用程序线程仅在安全点处停止,因此,当探查器以预定的时间间隔获取堆栈跟踪样本时,仅在下一个可用的安全点轮询位置检索堆栈跟踪。 因此,样本偏向安全点,分析器可能报告不准确的数据。
我们如何避免这种安全点偏差问题?
有一个AsyncGetCallTrace
方法,它是一个OpenJDK内部API调用,用于促进非安全点收集堆栈跟踪。 有一些分析器使用此AsyncGetCallTrace
方法来避免安全点偏差问题 。
Nitsan还在AsyncGetCallTrace
方法上写了一篇很棒的博客文章:“ AsyncGetCallTrace Profilers的优缺点 ”
“ 诚实剖析器 ”是第一个没有安全点偏差问题的Java采样剖析器。 Honest Profiler拥有自己的采样代理 ,该代理使用UNIX操作系统信号和AsyncGetCallTrace
API来高效,准确地分析Java应用程序。
Java Flight Recorder也不要求线程处于安全点才能对堆栈进行采样。 但是,如果不使用以下标志,它将无法获取代码中非安全点部分的元数据:“ -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
”
异步探查器简介
现在,您必须已经弄清楚“ Async Profiler ”还通过使用AsyncGetCallTrace
API避免了安全点偏差问题。
异步探查器 是Java的低开销采样分析器。 它结合使用AsyncGetCallTrace
和perf_events
来提供应用程序的更整体视图,包括系统代码路径和Java代码路径。 异步事件探查器可与任何基于HotSpot JVM(具有AsyncGetCallTrace
方法)的JDK配合使用,例如OpenJDK和Oracle JDK。
使用perf_events
,可以分析应用程序的系统代码路径。 Async Profiler很好地结合了来自perf_events
系统代码路径和来自AsyncGetCallTrace
API的Java代码路径。
在使用Async Profiler进行性能分析时,还建议使用以下标志:“ -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
”
异步事件探查器支持多种类型的分析。 例如,
- CPU分析
- 分配分析
- 挂钟分析
- 锁分析
Async Profiler的另一个激动人心的功能是开箱即用的火焰图支持,它可以可视化堆栈跟踪。
Flame Graph只是Async Profiler支持的输出格式之一。 有趣的是,Async Profiler还可以通过“方法配置样本”事件来生成Java飞行记录 。
以下是Async Profiler支持的输出类型。
- 摘要 :执行概要摘要
- traces :以样本计数的降序列出所有唯一的堆栈跟踪样本。
- flat :列出堆栈跟踪样本中的所有顶级方法。
- 折叠 :折叠的堆栈跟踪输出与Brendan Gregg的flamegraph.pl脚本兼容。
- svg :将所有堆栈轨迹可视化为火焰图。
- tree :Web(HTML)页面,将所有堆栈跟踪显示为调用树。
- jfr :带有方法概要分析样本事件的Java飞行记录
最后三个输出( svg , tree和jfr )应与将输出转储到文件的选项一起使用。
如果输出文件具有*.svg
, *.html
或*.jfr
扩展名,则输出格式将分别自动为svg , tree或jfr 。
summary , traces和flat输出可以组合。 这实际上是Async Profiler的默认输出。
异步事件探查器入门
如前所述,Async Profiler依赖于perf_events
。 应该执行以下配置以使用来自非根进程的perf_events
捕获内核调用堆栈。
- 将
/proc/sys/kernel/perf_event_paranoid
为1 。 - 将
/proc/sys/kernel/kptr_restrict
为0 。
第一个设置将允许一般用户分析内核。 第二个设置将禁用对公开内核地址的限制。
您可以将值直接写入提到的文件,也可以使用sysctl
命令更新值。
写入文件:
echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid
echo 0 | sudo tee /proc/sys/kernel/kptr_restrict
使用sysctl
命令:
sudo sysctl -w kernel.perf_event_paranoid=1
sudo sysctl -w kernel.kptr_restrict=0
您也可以在/etc/sysctl.conf
中添加以下行,以使设置永久化。 ( 通常不建议这样做 )
kernel.perf_event_paranoid =1
kernel. kptr_restrict =0
可以从GitHub版本页面下载最新的Async Profiler,或者您可以轻松构建Async Profiler。 异步分析器也包含在IntelliJ IDEA中 。
由于我喜欢使用最新的更改,因此我从源代码构建了Async Profiler。 构建探查器很容易。 我刚刚导出了JAVA_HOME
并执行了make
命令。
![](https://i-blog.csdnimg.cn/blog_migrate/1e7f9b667ba92eca29378b6e4ca1c825.png)
让我们对一些示例应用程序进行概要分析,并查看每个概要文件的外观。
我使用开发的示例应用程序来演示各种性能问题。 源代码位于https://github.com/chrishantha/sample-java-programs/
我将使用svg输出,因为使用Flame Graph可视化堆栈跟踪更加容易。
CPU分析
为了分析CPU,我使用了“ highcpu ”示例应用程序。 该应用程序具有多个线程,它们执行一些CPU消耗任务,例如为某些随机UUID
计算哈希并执行一些数学运算。
我使用以下命令执行了该应用程序。
java -Xms128m -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar target/highcpu.jar --exit -timeout 600
在应用程序运行时,我使用以下命令将CPU配置文件占用了30秒。
./profiler.sh -d 30 -f cpu-flame-graph.svg --title"CPU profile" --width 1600 $(pgrep -f highcpu)
以下是火焰图,显示了哪些方法在CPU上。
![](https://i-blog.csdnimg.cn/blog_migrate/933c2fe3137711b765473a584624035a.png)
在这里,我们可以看到Flame Graph同时显示了系统代码路径和Java代码路径。
没有Async Profiler,获得类似的Flame Graph并不容易。 为了生成“ Java混合模式火焰图 ”,必须执行以下步骤。
- 使用
-XX:+PreserveFramePointer
运行应用程序。 - 使用
perf record
命令分析应用程序,并生成一个perf.data
文件。 - 使用
perf-map-agent
生成Java符号表,以将Java代码地址映射到方法名称。 - 使用
perf script
命令,获取折叠输出,并生成Flame Graph。
这种方法有很多问题。 例如, -XX:+PreserveFramePointer
标志(仅在JDK 8u60及更高版本中可用)会产生性能开销。 有时,由于方法内联,框架将丢失。
分配分析
异步事件探查器还可用于探查分配堆内存的代码。 它使用TLAB
(线程本地分配缓冲区)回调,可以在生产中使用它而没有太多开销。 Java Flight Recorder也使用类似的方法。
让我们看一下相同highcpu示例应用程序的分配配置文件。 请注意,分析事件类型为alloc
。
./profiler.sh -e alloc -d 30 -f alloc-flame-graph.svg --title"Allocation profile" --width 1600 $(pgrep -f highcpu)
![](https://i-blog.csdnimg.cn/blog_migrate/a295bc63839cacf8e42984d1c37f1645.png)
现在,顶部边缘显示了分配内存的方法。 在这里,我们可以快速查看哪些代码路径在应用程序中分配内存。
让我们看看另一个名为“ allocations ”的示例应用程序。 此应用程序在有限循环中检查数字是否为质数。
与highcpu应用程序不同,此应用程序会在短时间内终止。 因此,我从一开始就通过将Async Profiler作为代理启动来配置分配。
java -Xms64m -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -agentpath:"/home/isuru/projects/git-projects/async-profiler/build/libasyncProfiler.so=start,event=alloc,file=allocation-flame-graph.svg,svg,title=Allocation profile,width=1600" -jar target/allocations.jar
以下是火焰图输出。
![](https://i-blog.csdnimg.cn/blog_migrate/e9610c5359cee034a2e4988559e7c5f1.png)
从“分配火焰图”可以清楚地看到,几乎所有分配都是由于Java自动装箱造成的 ,这在查看代码时不容易理解。
挂钟分析
通过使用wall
事件类型,Async Profiler还可同等地对所有线程进行采样,而与每个线程的状态(运行,睡眠或已阻止)无关。
挂钟配置文件对于确定线程随时间的变化非常有用。 挂钟分析可用于解决应用程序启动时间中的问题。
由于所有线程都进行了概要分析,而与线程状态无关,因此Wall-clock概要分析器在每个线程模式下最有用。
让我们来看一个例子。 以下是highcpu应用程序的挂钟简介。
./profiler.sh -e wall -t -d 30 -f wall-flame-graph.svg --title"Wall clock profile" --width 1600 $(pgrep -f highcpu)
![](https://i-blog.csdnimg.cn/blog_migrate/c1d8be499fffe5be1a7e4e743e89ddf9.png)
火焰图上方显示每个线程的样本数量几乎相等。
让我们看一下“ 延迟 ”应用程序的另一个挂钟配置文件。 该应用程序主要有两个线程(偶数线程和奇数线程)来打印偶数和奇数。 检查数字是否为偶数的方法已同步。
java -Xms64m -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -XX:+UseSerialGC -agentpath:"/home/isuru/projects/git-projects/async-profiler/build/libasyncProfiler.so=start,event=wall,file=wall-flame-graph.svg,threads,svg,simple,title=Wall clock profile,width=1600" -jar target/latencies.jar --count 10
![](https://i-blog.csdnimg.cn/blog_migrate/85b4d1c14293569c8b090178dfca454a.png)
火焰图上方清楚地显示了所有线程的线程状态。 偶数和奇数线程大部分时间处于“睡眠”或“阻塞”状态。
锁分析
可以使用Async Profiler中的lock
事件类型来分析Java应用程序中的lock
。
让我们看一下“延迟”应用程序的锁定配置文件。
java -Xms64m -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -XX:+UseSerialGC -agentpath:"/home/isuru/projects/git-projects/async-profiler/build/libasyncProfiler.so=start,event=lock,file=lock-flame-graph.svg,svg,simple,title=Lock profile,width=1600" -jar target/latencies.jar --count 10
![](https://i-blog.csdnimg.cn/blog_migrate/4f37f4b3e90323192123b8a68e1fd9a8.png)
火焰图上方显示了由于锁定而阻塞的代码路径。
其他类型的剖析
异步事件探查器还具有许多其他事件类型。 例如,它支持perf_events
一些硬件和软件性能计数器。
这意味着,使用Async Profiler,可以轻松地对诸如context-switches
, page-faults
等软件性能计数器以及诸如cache-misses
, branch-misses
等硬件性能计数器进行分析。
要查看目标JVM支持的所有事件类型,可以使用list
操作。
./profiler.sh list jps
Basic events:
cpu
alloc
lock
wall
itimer
Perf events:
page-faults
context-switches
cycles
instructions
cache-references
cache-misses
branches
branch-misses
bus-cycles
L1-dcache-load-misses
LLC-load-misses
dTLB-load-misses
mem:breakpoint
trace:tracepoint
摘要
到目前为止,Async Profiler可能是最好的性能分析工具。 它避免了安全点偏差问题,并结合了perf_events的Linux内核配置文件的功能。
Async Profiler支持许多事件,例如CPU周期,Linux性能计数器,分配,锁定尝试等。此故事演示了使用一些示例Java应用程序并生成Flame Graph的Async Profiler的一些示例。
使用Async Profiler真的很容易,尤其是因为它的输出要简单得多,并且不需要特殊的应用程序来处理输出。
我建议您尝试使用Async Profiler,并确保在开发过程中始终对应用程序进行配置(以避免将来产生昂贵的生产问题)。
翻译自: https://hackernoon.com/profiling-java-applications-with-async-profiler-049s2790
小程序使用async