async-profiler
本文涉及的圖片取自Profiling JVM Applications in Production
async-profiler
是一個對系統性能影響很少的Java採樣分析器,不會存在安全點偏差問題. 它具有特定於HotSpot的API,以收集堆棧跟蹤並跟蹤內存分配。探查器可與基於HotSpot JVM的OpenJDK,Oracle JDK和其他Java運行時一起使用。
async-profiler
可以跟蹤以下類型的事件:
- CPU週期
- 硬件和軟件性能計數器,例如高速緩存未命中,分支未命中,頁面錯誤,上下文切換等。
- Java堆中的分配
- 滿足的鎖定嘗試,包括Java對象監視器和ReentrantLocks
一、基本概念
1. JVMTI Agents
某些探查器使用openJDK內部API調用AsyncGetCallTrace(ASGT)便於非安全點收集堆棧跟蹤。AsyncGetCallTrace不是官方的JVM API。要使用ASGT,請先創建一個JVMTI Agents
JVMTI(JVM Tool Interface)是JVM提供的一套標準的C/C++編程接口,是實現Debugger、Profiler、Monitor、Thread Analyser等工具的統一基礎,在主流Java虛擬機中都有實現。
當我們要基於JVMTI實現一個Agent時,需要實現如下入口函數:
// $JAVA_HOME/include/jvmti.h
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved);
使用C/C++實現該函數,並將代碼編譯爲動態連接庫(Linux上是.so),通過-agentpath
參數將庫的完整路徑傳遞給Java進程,JVM就會在啓動階段的合適時機執行該函數。在函數內部,我們可以通過JavaVM指針參數拿到JNI和JVMTI的函數指針表,這樣我們就擁有了與JVM進行各種複雜交互的能力。
更多JVMTI相關的細節可以參考官方文檔。
2. AsyncGetCallTrace
第一個沒有安全點偏差問題的Java sampling profilers
, 用於非安全點收集堆棧跟蹤。生成單個線程的堆棧,而無需等待安全點。
二、分析
1. CPU profiling
在這種模式下,profiler 收集堆棧跟蹤示例,其中包括Java方法、native調用、JVM代碼和內核函數。
爲了能夠準確的生成Java和native代碼的確切性能報告,常用的方法是接收perf_events
生成的調用堆棧,並將它們與AsyncGetCallTrace
生成的調用堆棧進行匹配。此外Async-profiler還提供了一種可以在AsyncGetCallTrace失敗的某些情況下,恢復堆棧跟蹤的解決方法
2. ALLOCATION profiling
async-profiler
不使用侵入性技術,例如字節碼檢測工具或昂貴的DTrace探針,這些技術會對性能產生重大影響。它也不會影響轉義分析或防止JIT優化, 如分配消除。僅測量實際堆分配.
探查器具有TLAB驅動的採樣功能。它依賴於HotSpot特定的回調來接收兩種通知:
- 在新創建的TLAB中分配對象時(火焰圖中的淺綠色幀)
- 當在TLAB之外的慢路徑上分配對象時(棕色框架)
這意味着不對每個分配進行計數,而僅對每N kB進行分配,其中N是TLAB的平均大小。這使得堆採樣非常便宜並且適合於生產。另一方面,收集的數據可能不完整,儘管在實踐中通常會反映出最主要的分配來源
採樣間隔可以通過-i
選件進行調整。例如,-i 500k
平均分配500 KB的空間後,將採樣一個樣本。但是,小於TLAB大小的間隔不會生效。
三、與perf對比
1. perf
- 僅支持Java 8u60及更高版本(以禁用FPO)
- 禁用FPO對性能有較小的影響(在極端情況下最高可達10%)
- 方法名解析需要映射(map)文件:
perf-map-agent
- 不支持
interpreter frames
- 堆棧深度通常限制爲127 - 從Linux 4.8開始可以使用:
/proc/sys/kernel/perf_event_max_stack
- 支持系統範圍內的分析
2. async-profiler
與直接將perf_events
與Java代理一起使用相比較,該方式具有以下優點:
- 它適用於較舊的Java版本,因爲它不需要
-XX:+PreserveFramePointer
,這個參數只在JDK 8u60和更高版本中可用 - 不需要引入
-XX:+PreserveFramePointer
,因爲它可能導致較高的性能開銷,在極少數情況下可能高達10% - 不需要生成映射文件來將Java代碼地址映射到方法名
- 可以與
interpreter frames
一起工作 - 不需要爲了後續的進一步分析而需要生成perf.data文件
四、使用教程
1. 安裝
$ git clone https://github.com/jvm-profiling-tools/async-profiler
$ cd async-profiler
$ make
2. 參數
./profiler.sh [action] [options] <pid>
部分參數:
# Actions
start/stop # 開始分析/結束分析
list # 顯示可用分析事件的列表。此選項仍然需要PID,因爲受支持的事件可能因JVM版本而異
# Options
-d N # 分析持續時間
-e event # cpu, alloc, lock, cache-misses etc
-f filename # 將配置文件信息轉儲到的文件名 <filename>
3. 使用
a. 程序運行時動態載入
啓動Java應用程序,然後使用代理開始分析,收集性能情況然後停止分析
$ jps
9234 Jps
8983 Computey
$ ./profiler.sh start 8983
$ ./profiler.sh stop 8983
或者可以通過 -d
指定分析時間
$ ./profiler.sh -d 30 8983
b. 作爲代理啓動
如果您需要在JVM啓動後立即配置一些代碼,而不是使用profiler.sh
腳本,則可以在命令行上附加async-profiler
作爲代理。例如:
$ java -agentpath:/path/to/libasyncProfiler.so=start,file=profile.svg ...
4. 火焰圖可視化
async-profiler
提供了開箱即用的火焰圖支持。指定-o svg
參數以將分析結果轉儲爲交互式SVG,可在所有主流瀏覽器中立即查看。另外,如果目標文件名以結尾,則會自動選擇SVG輸出格式.svg
。
$ jps
9234 Jps
8983 Computey
$ ./profiler.sh -d 30 -f /tmp/flamegraph.svg 8983
五、實驗
本實驗參照該實驗手冊:linux-tracing-workshop
Task 1: Profiling Java Code
首先,運行我們將要分析的Java應用程序。 它是同一主要計數應用程序的Java版本:
$ java slowy/App
Press ENTER to start.
暫時不要按ENTER
, 在另一個shell中運行jps
找到Slowy應用程序的進程ID
$ jps
6043 App
6076 Jps
然後運行收集工具之後,在Java應用程序的控制檯中按ENTER,以便收集工具記錄一些有意義的工作
# 進入 目錄
./profiler.sh -d 15 6043
默認情況下,分析頻率爲100Hz(每10ms CPU時間)這是打印到Java應用程序終端的輸出示例,顯示Java程序中的瓶頸:slowy.App.isPrime
,導致該方法的最熱的調用堆棧來slowy.App.main
Started [cpu] profiling
--- Execution profile ---
Total samples : 376
Frame buffer usage : 0.0035%
--- 3729803417 ns (99.20%), 373 samples
[ 0] slowy.App.isPrime
[ 1] slowy.App.main
--- 10213575 ns (0.27%), 1 sample
[ 0] finish_task_switch_[k]
[...]
ns percent samples top
---------- ------- ------- ---
3729803417 99.20% 373 slowy.App.isPrime
10213575 0.27% 1 finish_task_switch_[k]
10013086 0.27% 1 Symbol::operator new(unsigned long, int, Arena*, Thread*)
9997484 0.27% 1 slowy.App.main
Task 2: Flame Graph visualization
同樣運行Java application, 並查看pid
$ jps
6841 Jps
6827 App
然後可以直接利用該命令,輸出火焰圖:
./profiler.sh -d 15 -f /tmp/flamegraph.svg 6827
ps: async-profiler 支持容器,perf 不支持,待驗證