天天看點

開源 Java 性能分析器比較:VisualVM、JMC 和 async-profiler

作者:InfoQ

作者 | Johannes Bechberger

譯者 | 平川

策劃 | 丁曉昀

在本文中,我将介紹性能分析的基本概念和不同類型的開源 Java 分析器,讓你可以根據自己的需要選擇最适合的分析器,并了解這些工具大緻的工作原理。

在2023年倫敦QCon演講“你的Java應用程式很慢嗎?試試這些開源分析器”中,我深入探讨過這個話題,也介紹了不同的性能檢視器。本文是基于那次演講整理而成。

分析器的目的是擷取有關程式執行的資訊,讓開發人員可以看到一個方法在給定的時間段内執行了多長時間。

但它們是如何做到這一點的呢?有兩種方法:程式插樁和抽樣。

插樁分析器

擷取性能分析概要的一種方法是,對于開發人員感興趣的每個方法,記錄其進入和退出時間。

當想要知道程式的特定部分花費了多長時間時,許多開發人員都會使用這種檢測方法。

在這種方法中,下面的方法:

void methodA() {
      // … // 做工作
}           

複制代碼

會被修改成:

void methodA() {
      long start = System.currentTimeMillis();
      // … // 做工作
      long duration = System.currentTimeMillis() - start;
      System.out.println(“methodA took “ + duration + “ms”);
}           

複制代碼

這種修改可以用于基本的時間測量。盡管如此,在嵌套測量方法時,它提供的資訊很少,因為了解方法之間的關系也很有趣,例如methodB()由methodA()在幾秒鐘内執行。是以,我們需要記錄每次進入和退出相關方法的日志。這些日志會關聯到時間戳和目前線程。

插樁分析器的思想是将這種代碼修改的過程自動化:它将logEntry()和logExit()方法的調用插入到方法的位元組碼中。這些方法是分析器運作時庫的一部分。通常,這種插入是在運作時完成的,即在類加載時通過插樁代理完成。然後,分析器将methodA()修改為:

void methodA() {
      logEntry(“methodA”);
      // … // 做工作
      logExit(“methodA”);
}           

複制代碼

插樁分析器的優點是它們對所有 JVM 都有效,因為它們可以用純 Java 實作。但它們有一個缺點,即插入的方法調用會導緻顯著的性能損失并嚴重影響結果。是以,在最近幾十年裡,純插樁分析器的流行度已然消退。如今,現代分析器大多都是抽樣分析器。

抽樣分析器

另一種分析器是抽樣分析器,它們會在被分析程式執行時進行抽樣。這類分析器會定期向 JVM 請求目前運作程式的堆棧,通常是每 10 毫秒到 20 毫秒一次。然後,分析器會使用這些資訊來估算性能。這種方法的主要缺點是:運作時間比較短的方法可能不會在性能分析概要中出現。

抽樣分析器的主要優點是:它們不會修改程式,開銷比較小,不會對結果産生明顯的影響。

現代抽樣分析器通常每 10 到 20 毫秒一次循環運作以下代碼:

開源 Java 性能分析器比較:VisualVM、JMC 和 async-profiler

抽樣分析器每次疊代都會擷取目前的(Java)線程清單。然後,它會随機選擇一個線程子集進行抽樣。通常,這個子集的大小在 5 到 8 之間,因為每次疊代對太多線程進行抽樣會增加運作分析器的性能影響。在分析具有大量線程的應用程式時,請注意這一點。

然後,分析器向每個標明的線程發送一個信号,這将導緻它們停下來調用信号處理程式。此信号處理程式會擷取并存儲其線程的堆棧跟蹤。在每次疊代結束時,分析器會收集所有堆棧跟蹤資訊并進行後處理。

實作抽樣分析器還有其他的方法,但我這裡介紹的是使用最廣泛且精度最佳的技術。

不同的開源分析器

目前,最著名的開源分析器有 3 個:VisualVM、async-profiler 和 JDK Flight Recorder(JFR)。這些分析器都處于積極開發過程中,可用于各種應用程式。它們都是抽樣分析器。VisualVM 是唯一支援插樁分析的分析器。

開源 Java 性能分析器比較:VisualVM、JMC 和 async-profiler

我們可以區分下“外部”和“内置”分析器:外部分析器不是直接實作到 JVM 中,而是使用 API 來收集特定線程的堆棧跟蹤資訊。對于隻使用 API 的分析器,同一個版本可以用于不同的 JVM 版本和供應商(如 OpenJDK 和 OpenJ9)。

最著名的外部分析器有兩個:VisualVM 和 async-profiler;它們的主要差別在于它們使用的 API。VisualVM 使用官方的Java管理擴充(JMX)來擷取線程的堆棧跟蹤資訊。另一方面,async-profiler 使用非官方的 AsyncGetCallTrace API。兩者各有優缺點,但通常,JMX 及相關 API 被認為更安全,而 AsyncGetCallTrace 更精确。

OpenJDK 和 GraalVM 僅有一個内置分析器 Java Flight Recorder(JFR);它的工作原理與 async-profiler 大緻相同,同樣精确,但更穩定。

接下來,我将介紹這幾個分析器及其曆史。

VisualVM

該工具是 Netbeans 分析器的獨立版本。從 2006 年的 Oracle JDK 6 到 JDK 8,每個 JDK 都包含 Java VisualVM 工具。該工具于2008年開源。後來,這個分析器更名為 VisualVM,Oracle JDK 9 不再包含它。根據JetBrains最近的一項調查,VisualVM 是最常用的開源分析器。需要的話,可以從這裡下載下傳。

它的用法很簡單;隻需要在 GUI 中為你想要分析的程式選擇運作它的 JVM 并啟動性能分析:

開源 Java 性能分析器比較:VisualVM、JMC 和 async-profiler

然後,你可以在一個簡單的樹形可視化中直接檢視性能分析概要資訊。也可以從指令行啟動和停止抽樣分析器:

visualvm --start-cpu-sampler <pid>
visualvm --stop-sampler <pid>           

複制代碼

VisualVM 提供了易于使用的簡單 UI,但需要注意,它使用了不太精确的 JVM API。

Async-Profiler

Async-profiler 是最常用的分析器之一,這不僅僅是因為它被嵌入到了許多其他工具中,如 IntelliJ Ultimate Profiler 和 AppIication Performance Monitors。你可以從項目的GitHub頁面下載下傳 async-profiler。它包含特定于平台的二進制檔案,不支援 Windows。是以,我建立了app-loader項目,将所有 async-profiler 二進制檔案封裝到一個多平台二進制檔案中,使得嵌入并使用這個分析器變得更容易。

你可以通過許多嵌入了 async-profiler 的工具使用它,或直接将其作為本機 Java 代理來使用。假設你下載下傳了特定于平台的 libasyncProfiler.so,則隻需在調用 Java 二進制檔案時添加以下選項,即可分析 Java 應用程式的性能:

java 
-agentpath:libasyncProfiler.so=start,event=cpu,file=flame.html,flamegraph …           

複制代碼

這個調用告訴 async-profiler 生成一個火焰圖。這是一種非常流行的可視化方式。

你也可以用它建立 JFR 檔案:

java 
-agentpath:libasyncProfiler.so=start,event=cpu,file=profile.jfr,jfr …           

複制代碼

這個調用讓你可以在衆多檢視器中檢視性能分析概要檔案。

以下是 async-profiler 的發展簡史,感興趣的可以了解一下。

2002 年 11 月,Sun(後來被 Oracle 收購)根據JVM(TM)工具接口規範将 AsyncGetStackTrace API 添加到 JDK 中。新 API 使得從外部分析器獲得精确的堆棧跟蹤資訊成為可能。Sun 引入這個 API 是為了給他們的 Sun Development Studio 添加一個完整的 Java 分析器。然而,兩個月後,他們删除了該 API,原因未公開。但是,這個 API 仍然以 AsyncGetCallTrace 的形式保留在 JDK 中,直到今天一直存在,隻是沒有導出,是以比較難用。

幾年後,人們偶然發現,這個 API 是一個不錯的實作分析器的方法。2007 年,Jeremy Manson 在博文“使用JVMTI/JVMPI、SIGPROF和AsyncGetCallTrace進行性能分析”中,首次提到将 AsyncGetCallTrace 作為實作 Java 分析器的基礎。從那時起,許多開源和閉源分析器就開始使用它。YourKit、JProfiler和honest-profiler是其中幾個比較有名的例子。Async-profiler 的開發始于 2016 年;它目前是使用 AsyncGetCallTrace 的最主要的開源分析器。

Async-profiler 的問題在于,它是基于一個非官方的内部 API。這個 API 沒有經過官方 OpenJDK 測試套件的充分測試,随時都可能失效。盡管該 API 的廣泛應用使得它已近乎标準化,但這仍然是一個風險。為了減輕這些風險,我目前正在編制一份 JDK 增強提案,在 OpenJDK 中增加一個官方的 AsyncGetCallTrace 版本;見JEP候選435。

Async-profiler 的優勢在于它的許多特性(如堆采樣)、可嵌入性、對其他 JVM(如 OpenJ9)的支援,以及它小巧的代碼庫,這使得它的适應性非常好。要了解關于 async-profiler 的更多資訊,可以檢視async-profiler自述檔案、async-profiler維基以及 Krzysztof Ślusarski 提供的async-profiler實用手冊。

JDK Flight Recorder(JFR)

JRockit 最初開發運作時分析器是為了内部使用,但它也越來越受應用程式開發人員的歡迎。後來,在 Oracle 收購了其開發公司之後,這些特性被內建到了 Oracle JDK 中。最終,Oracle 将該工具與 JDK11 一起開源,從那時起,它就成了 OpenJDK JVM 的内置分析工具,不再支援 OpenJ9 等其他 JVM 了。

它的工作原理與 async-profiler 類似,主要差別是它直接使用内部的 JVM API。該分析器的使用很簡單,可以通過在 Java 二進制檔案的調用中添加以下選項:

$ java \
  -XX:+UnlockDiagnosticVMOptions \
  -XX:+DebugNonSafepoints \  # improves precision
  -XX:+FlightRecorder \
  -XX:StartFlightRecording=filename=file.jfr \
  arguments           

複制代碼

或者使用 JDK 指令行工具jcmd 啟動和禁用它:

$ jcmd PID JFR.start
$ jcmd PID JFR.dump filename=file.jfr
$ jcmd PID JFR.stop           

複制代碼

JFR 捕獲許多性能分析事件,從堆棧跟蹤資訊抽樣到垃圾收集和類加載統計資訊。JFR事件網站上提供了所有事件的清單。我們甚至還可以添加自定義事件。

要了解更多關于這個工具的資訊,可以閱讀JDK Flight Recorder、The Programmatic Way(來自 BellSoft)等部落格的文章。

與 async-profiler 相比,JFR 的主要優勢是它存在于所有平台的 OpenJDK 中,甚至在 Windows 上。此外,JFR 更穩定一些,記錄的事件和資訊也更多。JFR 有一個名為 JDK 任務控制的 GUI,它讓你可以分析 JVM 性能并檢視生成的 JFR 性能分析概要。

正确性與穩定性

在使用我所介紹的分析器時,務請記住以下内容:它們本身也是軟體,與大型項目 OpenJDK(或 OpenJ9)交織在一起,是以,它們也會遇到與它們所分析應用程式相同的典型問題:

  • 測試可以更豐富,特别是底層 API,可以更好地測試一下;目前隻有一個測試。(我正在努力)
  • 測試可以做得更好:現有的測試甚至沒有充分測試 API 是否适用于小樣本。它隻檢查了最上面的幀,但忽略了傳回的跟蹤資訊太短這個問題。我發現了這個問題并修複了測試用例。
  • 缺乏自動化回歸測試:缺乏測試還意味着,對目前項目中看似不相關部分的更改可能會對分析産生不利的影響,而又沒有人注意到。

是以,對于分析器生成的性能分析概要,你要持保留态度。以下博文和演講談及了分析器的準确性問題:

  • 分析器都是撒謊的霍比特人
  • 内聯代碼如何導緻性能分析概要的混亂
  • 為什麼JVM現代分析器仍然有安全點偏見?
  • Java性能分析API驗證

此外,在極少數情況下,對應用程式進行性能分析還可能導緻 JVM 崩潰。像Jaroslav Bachorik和我這樣的 OpenJDK 開發人員正設法盡可能地修複底層分析 API 中存在的所有穩定性問題。在實踐中,使用上面提到的任何一種分析器都是安全的,很少會引發崩潰。如果遇到問題,請聯系分析程式開發人員或在相應的存儲庫中開一個 GitHub 問題。

小結

現代基于抽樣的 Java 分析器使得使用開源工具調查性能問題成為可能。你可以選擇:

  • 一個稍微有點不精确但易于使用并且提供了簡單 UI 的工具(VisualVM)
  • 一個内置的工具,提供包括 GC 資訊在内的更多資訊(JFR)
  • 一個提供很多選項的工具,可以顯示 C/C++代碼的資訊(async-profiler)

都試用一下,以便了解在下一次遇到性能問題時使用哪種工具。

原文連結:

https://www.infoq.com/articles/open-source-java-profilers/

本文轉載來源:

https://www.infoq.cn/article/yO6pjms5izsxK5YrZ036