Async-profiler介紹

1、介紹

Async-profiler是一個對系統性能影響很少的Java採樣分析器,它的實現是基於HotSpot特有的API,通過這些特有的API收集堆棧跟蹤和跟蹤內存分配,因而其可以和OpenJDK、Oracle JDK和其他基於HotSpot JVM的Java應用在運行時協同工作。

Github項目鏈接地址:https://github.com/jvm-profiling-tools/async-profiler

Async-profiler可以跟蹤以下類型的事件:

  • CPU週期;
  • 硬件和軟件性能計數器,如緩存未命中、分支未命中、頁面錯誤、上下文切換等;
  • Java堆中的分配;
  • 滿足的鎖定嘗試,包括Java對象監視器和可重入鎖;

支持的平臺

  • Linux / x64 / x86 / ARM / AArch64
  • macOS / x64

注意:macOS分析僅限於用戶空間代碼。

2、CPU性能分析

在此模式下,profiler收集堆棧跟蹤示例,其中包括Java方法、native調用、JVM代碼和內核函數。

為了能夠準確的生成Java和native代碼的確切性能報告,常用的方法是接收perf_events生成的調用堆棧,並將它們與AsyncGetCallTrace生成的調用堆棧進行匹配。此外Async-profiler還提供了一種可以在AsyncGetCallTrace失敗的某些情況下,恢復堆棧跟蹤的解決方法。

與將地址轉換為Java方法名稱的Java代理相比較,使用perf_eventst的方式具有以下優點:

  • 它適用於較舊的Java版本,因為它不需要-XX:+PreserveFramePointer,這個參數只在JDK 8u60和更高版本中可用;
  • 不需要引入-XX:+PreserveFramePointer,因為它可能導致較高的性能開銷,在極少數情況下可能高達10%;
  • 不需要生成映射文件來將Java代碼地址映射到方法名;
  • 使用解釋器幀;
  • 不需要為了後續的進一步分析而需要生成perf.data文件;

3、堆內存分配分析

async-profiler使用的分析技術對系統的性能影響不大,它不像字節碼檢測或DTrace探測等會對系統性能可能產生較大的影響。它也不影響轉義分析或防止JIT優化,如分配消除,async-profiler只測量實際的堆分配。

分析器具有TLAB(Thread Local Allocation Buffer,即線程本地分配緩存區)驅動的採樣功能,它依賴於HotSpot特定的回調來接收以下兩種TLAB通知:

  • 新創建的TLAB中分配對象時;
  • 在TLAB外部的慢速路徑上分配對象時。

這意味分析器不會對每個TLAB分配進行計算,而只會計算每N kB的分配,其中N是TLAB的平均大小。這使得堆採樣非常輕量,也適合於生產環境使用。雖然這種收集方式也可能會導致收集的數據可能是不完整的,不過根據實踐經驗,這種收集方式通常能夠反映頂級分配來源。

採樣間隔可以使用-i選項進行調整,例如,-i 500k將在平均分配了500kb空間後獲取一個樣本。但是,小於TLAB大小的間隔將不會生效。

與使用類似方法的Java任務控制不同,async-profiler不需要Java Flight Recorder或任何JDK的其他商業特性,它完全基於開源技術,並與OpenJDK一起工作。

注:如需要收集TLAB的相關信息,JDK的最低版本要求是7u40,大於等於這些版本的JDK才有TLAB回調功能。

堆分析器需要HotSpot調試符號,Oracle JDK已經將它們嵌入到libjvm.so中,但是OpenJDK在構建時,被打包到了單獨的包中,如要在Debian/Ubuntu上安裝OpenJDK調試符號,請運行:

apt install openjdk-8-dbg

或者對於OpenJDK在CentOS、RHEL和其他一些基於RPM的發行版上,這可以使用debuginfo-install來安裝:

debuginfo-install java-1.8.0-openjdk

4、Wall-clock分析
選項-e wall告訴async-profiler在給定的時間段內對所有線程進行平均採樣,可以對運行、休眠或阻塞的線程進行採樣,如需要分析應用程序啟動時間時,可以使用該選項。

在per-thread模式下,Wall-clock分析更能夠發揮其作用,通過加入-t參數開啟該模式,示例:

./profiler.sh -e wall -t -i 5ms -f result.svg 8983

5、編譯
編譯async-profiler,需要以下條件:

  • JAVA HOME環境變量,且指向JDK安裝路徑;
  • GCC(可以通過如apt install gcc等進行安裝)。

然後通過make命令進行打包,編譯後的代理二進制文件將位於生成子目錄中,與此同時,可以將代理加載到目標進程中的小型應用程序jattach也將編譯到build子目錄中。

6、基本用法

從Linux4.6開始,如果需要使用非root用戶啟動的進程中的perf_events,捕獲內核調用堆棧的信息,需要設置兩個系統運行時變量,可以使用sysctl或按如下方式設置它們:

echo 1 > /proc/sys/kernel/perf_event_paranoid
echo 0 > /proc/sys/kernel/kptr_restrict

async-profiler通過profiler.sh腳本進行啟動,並向需要分析的應用程序傳遞命令,典型的工作流:

  1. 啟動Java應用程序;
  2. 附加代理並開始分析;
  3. 運行性能場景;
  4. 停止分析。

代理的輸出(包括分析結果)將顯示在Java應用程序的標準輸出中。

示例:

$ jps
9234 Jps
8983 Computey
$ ./profiler.sh start 8983
$ ./profiler.sh stop 8983

也可以通過-d(duration)參數指定分析的時間,以秒為單位:

$ ./profiler.sh -d 30 8983

默認情況下,分析頻率為100Hz(每10ms CPU時間),下面是輸出到Java應用程序終端的輸出示例:

--- Execution profile ---
Total samples: 687
Unknown (native): 1 (0.15%)


--- 6790000000 (98.84%) ns, 679 samples
[ 0] Primes.isPrime
[ 1] Primes.primesThread
[ 2] Primes.access$000
[ 3] Primes$1.run
[ 4] java.lang.Thread.run

... a lot of output omitted for brevity ...

ns percent samples top
---------- ------- ------- ---
6790000000 98.84% 679 Primes.isPrime
40000000 0.58% 4 __do_softirq

... more output omitted ...

這表明受影響最大的方法是Primes.isPrime,其是被Primes.primesThread線程調用的。

7、以Agent的方式啟動

如果需要在JVM啟動後立即分析一些代碼,而不是待應用程序啟動完成後再使用profiler.sh腳本進行分析,則可以在命令行上附加async-profiler作為代理。例如:

$ java -agentpath:/path/to/libasyncProfiler.so=start,file=profile.svg ...

Agent庫是通過JVMTI參數接口配置的,參數字符串的格式在源代碼中描述,profiler.sh腳本實際上將命令行參數轉換為該格式。

例如:

-e alloc會被轉換為event=alloc;
-f profile.svg會被轉換為file=profile.svg等等。

但是有些參數是由profiler.sh腳本直接處理的。例如參數-d 5將導致3個操作:

  1. 使用start命令附加探查器agent;
  2. 休眠5秒;
  3. 然後使用stop命令再次附加代理。

8、查看火焰圖

async-profiler提供開箱即用的Flame圖形支持,指定參數-o svg以將分析結果轉儲為可在所有主流瀏覽器中查看的交互式svg圖像。另外,如果目標文件名以.SVG結尾,則會自動選擇SVG輸出格式。

如下命令:

$ jps
9234 Jps
8983 Computey
$ ./profiler.sh -d 30 -f /tmp/flamegraph.svg 8983

可能會生成如下火焰圖:

Async-profiler介紹

9、分析選項參數

下面是profiler.sh腳本接受的命令行選項的完整列表:

start - 以半自動模式開始分析,即在顯式調用stop命令之前,分析程序將會一直運行;
resume - 啟動或恢復先前已停止的分析會話,前面所有收集的數據仍然有效,分析選項不會在會話之間保留,應再次指定;
stop - 停止分析並打印報告;
status - 打印分析狀態:分析程序是否處於活動狀態以及持續多長時間;
list - 顯示可用分析事件的列表,此選項仍然需要PID,因為支持的事件可能因JVM版本而異;
-d N - 分析持續時間,以秒為單位。如果未提供start、resume、stop或status選項,則探查器將在指定的時間段內運行,然後自動停止,示例:./profiler.sh - d 30 8983

-e event - 指定要分析的事件,如:cpu、alloc、lock、cache misses等。使用list參數查看可用事件的完整列表。
在分配(alloc)分析模式中,每個調用跟蹤的頂部框架是已分配對象的類,計數器是堆中的記錄(已分配TLAB或TLAB之外的對象的總大小)。
在鎖(lock)分析模式下,頂部框架是鎖/監視器的類,計數器是進入此鎖/監視器所需的納秒數。

Linux上支持兩種特殊事件類型:硬件斷點和內核跟蹤點:
-e mem:<func>[:rwx] 在函數<func>處設置讀/寫/執行斷點。mem事件的格式與perf-record相同。執行斷點也可以由函數名指定,例如-e malloc將跟蹤本地malloc函數的所有調用;
-e trace: 設置內核跟蹤點。可以指定跟蹤點符號名,例如-e syscalls:sys_enter_open將跟蹤所有打開的系統調用;

-i N - 後面跟ms(毫秒)、us(微秒)或s(秒),則以納秒或其他單位設置分析間隔。僅計算CPU活動的時間,CPU空閒時不收集樣本,默認值為10000000(10ms)。
示例:./profiler.sh - i 500us 8983

-j N - 設置Java堆棧分析深度。如果N大於默認值2048,則將忽略此選項。
示例:./profiler.sh - j 30 8983

-b N - 設置幀緩衝區大小,以緩衝區中應該容納的Java方法id的數量為單位。如果接收到有關幀緩衝區大小不足的消息,請將此值從默認值增加,示例:./profiler.sh - b 5000000 8983

-t - 對每個線程進行單獨分析,每個堆棧跟蹤都將以表示單個線程的幀結束,示例:./profiler.sh - t 8983
-s - 打印簡單類名而不是FQN(Full qulified name全類名);
-g - 打印方法簽名;
-a - 通過添加_[j]後綴來註釋Java方法名;
-o fmt - 指定分析結束時要轉儲的信息。fmt可以是以下選項之一:

summary - 轉儲基本配置統計信息;
traces[=N] - 轉儲調用跟蹤(最多N個樣本);
flat[=N] - dump flat profile(調用最多的前面N個方法);

jfr - 以Java Mission Control可讀的Java Flight Recorder格式轉儲事件,這不需要啟用JDK商業功能;
collapsed[=C] - 以FlameGraph腳本使用的格式轉儲調用跟蹤的結果,這是調用堆棧的集合,其中每一行是一個分號分隔的幀列表,後跟一個計數器。
svg[=C] - 生成svg格式的火焰圖。
tree[=C] - 以HTML格式生成調用樹。
--reverse 該選項將生成回溯視圖。
C是計數器類型:
samples - 計數器是給定跟蹤的若干樣本;
total - 計數器是收集的度量的總值,例如總分配大小。
小結,跟蹤和展開可以結合在一起。
默認格式是summary,traces=200,flat=200。

--title TITLE,--width PX,--height PX,-- minwidth PX,--reverse -FlameGraph參數;
示例:./profiler.sh - f profile.svg--title "示例CPU配置文件" --minwidth 0.58983

-f FILENAME - 要將配置文件信息轉儲到的文件名。
%p - 被擴展到目標JVM的PID;
%t - 到命令調用時的時間戳。
示例: ./profiler.sh -o collapsed -f /tmp/traces-%t.txt 8983
--all-user - 僅包括用戶模式事件。當內核分析受perf_event_paranoid設置限制時,此選項非常有用。

--all-kernel 表示只包含內核模式事件。
--sync-walk - 首選同步JVMTI堆棧walker,而不是AsyncGetCallTrace。此選項可以提高分析JVM運行時函數(如VMThread::execute、G1CollectedHeap::humongus_obj_allocate等)時Java堆棧跟蹤的準確性,除非您絕對確定,否則不要使用!如果使用不當,此模式將導致JVM崩潰!
-v,--version - 打印探查器庫的版本,如果指定了PID,則獲取加載到給定進程中的庫的版本。
/<func>/<func>

10、分析容器中的Java應用程序

可以從容器內部和主機系統分析在Docker或LXC容器中運行的Java進程。

從主機進行分析時,pid應該是主機命名空間中的Java進程ID。使用ps aux | grep java或docker top<container>查找進程ID。/<container>

async-profiler應該由特權用戶從主機運行 - 它將自動切換到正確的pid/裝載命名空間,並更改用戶憑據以匹配目標進程。還要確保目標容器可以通過與主機上相同的絕對路徑訪問libasyncProfiler.so。

默認情況下,Docker container限制對perf_event_open syscall的訪問。因此,為了允許在容器中進行分析,您需要修改seccomp配置文件,或者使用--security-opt seccomp=unconfined選項完全禁用它。此外,可能需要,--cap-add SYS_ADMIN。

或者,如果無法更改Docker配置,則可以返回到-e itimer分析模式,請參閱疑難解答。

11、限制

  • 在大多數Linux系統中,perf-events捕獲最大堆棧深度為127幀的調用堆棧,在最新的Linux內核上,這可以使用sysctl kernel.perf_event_max_stack或通過寫入/proc/sys/kernel/perf_event_max_stack文件來配置;
  • Profiler為目標進程的每個線程分配8kB性能事件緩衝區,在非特權用戶下運行時,請確保/proc/sys/kernel/perf_event_mlock_kb值足夠大(大於8*總的線程數),否則將打印消息“perf_event mmap failed:Operation not allowed”,並且不會收集本機堆棧跟蹤;
  • 無法保證perf_events overflow信號以保證沒有其他代碼運行的方式傳遞到Java線程,這意味著在某些罕見的情況下,捕獲的Java堆棧可能與捕獲的本機(用戶+內核)堆棧不匹配;
  • 在堆棧的Java幀之前,您不會看到非Java幀,例如,如果start_thread調用JavaMain,然後Java代碼開始運行,則在生成的堆棧中不會看到前兩個幀。另一方面,您將看到Java代碼調用的非Java框架(用戶和內核);
  • 如果-XX:MaxJavaStackTraceDepth參數設置為0或為負數,則不會收集Java堆棧;
  • 分析間隔太短,可能會被clone()之類佔用系統資源較多的方法給中斷,因此它起不到收集數據並分析的目的,請參閱#97 issue,解決辦法只是增加間隔;
  • 如果在JVM啟動時未加載代理(通過使用-agentpath選項),強烈建議使用-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints JVM標誌,如果沒有這些標誌,分析器仍然可以正常工作,但結果可能不太準確,例如,如果沒有-XX:+DebugNonSafepoints,則很有可能簡單的內聯方法不會出現在概要文件中。當代理在運行時附加CompiledMethodLoad時,JVMTI事件啟用調試信息,但僅適用於在事件打開後編譯的方法;

12、常見問題

1)Failed to change credentials to match the target process: Operation not permitted

由於HotSpot動態附加機制的限制,Profiler必須由與目標JVM進程所有者完全相同的用戶(和組)運行,如果探查器由其他用戶運行,它將嘗試自動更改當前用戶和組,對於根用戶這可能會成功,但對於其他用戶則不會成功,從而導致上述錯誤。

2)Could not start attach mechanism: No such file or directory

Profiler無法通過UNIX域套接字與目標JVM建立通信,通常發生在下列情況之一:

  • socket套接字連接/tmp/.java_pidNNN被刪除Attach,可能由於其中/tmp/目錄下,被其它的系統清除程序給刪除了,檢查可通過如下命令:
lsof -p PID | grep java_pid

如果它列出了一個套接字文件,但是文件不存在,那麼這就是所描述的問題;

  • JVM以-XX:+DisableAttachMechanism選項啟動;
  • Java進程的/tmp目錄在物理上與shell的/tmp目錄不同,因為Java是在容器或chroot環境中運行的。jattach試圖自動解決這個問題,但它可能缺少這樣做所需的權限可通過如下命令進行檢查:
strace build/jattach PID properties
  • JVM正忙,無法到達安全點,例如,JVM正在進行長時間的垃圾收集,檢查當前JVM是否繁忙的命令:
kill-3 PID

運行良好的JVM進程應該在其控制檯中打印線程轉儲和堆信息;

3)Failed to inject profiler into

已建立與目標JVM的連接,但JVM無法加載探查器共享庫,確保JVM進程的用戶具有訪問libasyncProfiler.so的權限,訪問的絕對路徑完全相同。有關詳細信息,請參見#78 Issue。

4)Perf events unavailble. See stderr of the target process.

perf_event_open()系統調用失敗,錯誤消息被打印到目標JVM的錯誤流中。
典型原因包括:

  • /proc/sys/kernel/perf_event_paranoid設置為受限模式(>=2);
  • seccomp禁用容器中的perf_event_open API;
  • 操作系統在不虛擬化性能計數器的管理程序下運行;
  • 當前系統不支持perf_event_open API,例如WSL。

如果無法更改配置,則可以返回到使用-e itimer分析模式。它類似於cpu模式,但不需要性能事件支持,但是其存在一個不足,不能夠收集內核堆棧跟蹤的信息;

5)No AllocTracer symbols found. Are JDK debug symbols installed?

可能需要安裝帶有OpenJDK調試符號的包,有關詳細信息,請參閱分配分析。

注意,除了HotSpot(例如Zing)JVM支持之外,其它的JVM都不支持分配分析。

6)VMStructs unavailable. Unsupported JVM?

JVM共享庫未導出gHotSpotVMStructs*符號-顯然這不是一個HotSpot JVM。有時,錯誤構建的JDK也可能導致相同的消息(請參見218 Issue),在這些情況下,安裝JDK調試符號可以解決問題;

7)Could not parse symbols due to the OS bug

Async-profiler無法分析非Java函數名,因為/proc/[pid]/maps中的內容已損壞,眾所周知,使用Linux內核5.x運行Ubuntu時,容器中會出現此問題。這是操作系統錯誤,請參閱https://bugs.launchpad.net/Ubuntu/+source/Linux/+bug/1843018。


8、[frame_buffer_overflow]

輸出中的此消息表示沒有足夠的空間存儲所有調用跟蹤,考慮使用-b選項增加幀緩衝區大小。


分享到:


相關文章: