1.Java 監控工具
Java 不僅僅是一種編程語言,而是一個非常豐富的生態系統,它有很多工具。JDK 包含的程序,允許我們編譯自己的程序,以及監視其狀態和 Java 虛擬機在程序執行的完整生命週期內的狀態。
JDK 提供的的 bin 文件夾包含可用於分析和監視的以下程序:
- Java VisualVM (jvisualvm.exe)
- JConsole (jconsole.exe)
- Java Mission Control (jmc.exe)
- Diagnostic Command Tool (jcmd.exe)
我們建議瀏覽此文件夾的內容,瞭解我們掌握的工具。
在本教程中,我們將重點介紹 Java 飛行記錄器。上述工具中不存在此,因爲它不是獨立程序。它的使用與上述兩個工具密切相關 - Java 任務控制和診斷命令工具。
2.基本概念
Java 飛行記錄器 (JFR) 是一種監視工具,用於在 Java 應用程序執行期間收集有關 Java 虛擬機 (JVM) 中事件的信息。JFR 是 JDK 分佈的一部分,並集成到 JVM 中。
JFR 旨在儘可能少地影響正在運行的應用程序的性能。
爲了使用 JFR,我們應該激活它。我們可以通過兩種方式實現此目的:
啓動Java應用程序時
在 Java 應用程序已運行時傳遞 jcmd 工具的診斷命令
JFR 沒有獨立工具。我們使用 Java 任務控制 (JMC),它包含一個插件,允許我們可視化 JFR 收集的數據。
這三個組件(JFR、jcmd 和 JMC)構成了一套完整的套件,用於收集正在運行的 Java 程序的低級運行時信息。在優化程序或診斷程序時,我們可能會發現此信息非常有用。
如果我們的計算機上安裝了各種版本的 Java,請務必確保 Java 編譯器 (javac)、Java 啓動器 (java) 和上述工具(JFR、jcmd 和 JMC)來自同一 Java 版本。否則,由於不同版本的 JFR 數據格式可能不兼容,因此存在無法看到任何有用數據的風險。
JFR 有兩個主要概念:事件和數據流。讓我們簡要地討論一下。
- Events
JFR 收集運行 Java 應用程序時在 JVM 中發生的事件。這些事件與 JVM 本身的狀態或程序的狀態相關。事件具有名稱、時間戳和其他信息(如線程信息、執行堆棧和堆的狀態)。
JFR 收集三種類型的事件:
- 即時事件一旦發生,將立即記錄
- 如果持續時間事件成功指定閾值,則記錄持續時間事件
- 示例事件用於對系統活動進行採樣
- Dataflow
JFR 收集的事件包含大量數據。因此,根據設計,JFR 的速度足夠快,不會妨礙程序。
JFR 將有關事件的數據保存在單個輸出文件 flight.jfr 中。
正如我們所知,磁盤 I/O 操作相當昂貴。因此,JFR 使用各種緩衝區來存儲收集的數據,然後再將數據塊刷新到磁盤。事情可能會變得稍微複雜一點,因爲在同一時刻,一個程序可能有多個具有不同選項的註冊進程。
因此,我們可能會在輸出文件中找到比請求更多的數據,或者它可能不是按時間順序排列的。如果我們使用 JMC,我們甚至可能沒有注意到這一事實,因爲它按時間順序顯示事件。
在某些極少數情況下,JFR 可能無法刷新數據(例如,當事件過多或斷電時)。如果發生這種情況,JFR 會嘗試通知我們輸出文件可能缺少一段數據。
3.如何使用JFR
JFR 是一個實驗功能,因此其使用可能會發生變化。事實上,在早期的發行中,我們必須激活商業功能,以便在生產中使用。但是,從 JDK 11 開始,我們可能會使用它而不激活任何內容。我們始終可以查閱官方的 Java 發行說明,以檢查如何使用此工具。
對於 JDK 8,爲了能夠激活 JFR,我們應該使用選項 " +UnlockCommercialFeatures "和 " +FlightRecorder "啓動 JVM。
如上所述,有兩種方法可以激活 JFR。當我們在啓動應用程序時同時激活它時,我們從命令行執行。當應用程序已在運行時,我們使用診斷命令工具。
3.1 命令行
首先,我們使用標準java編譯器javac將程序的*.java文件編譯成一個*.class。
編譯成功後,我們可以使用以下選項啓動程序:
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
-XX:StartFlightRecording=duration=200s,filename=flight.jfr path-to-class-file
其中到類文件的路徑是應用程序的入口點 _.class 文件。
此命令啓動應用程序並激活錄製,錄製將立即啓動,持續不超過 200 秒。收集的數據保存在輸出文件 flight.jfr 中。我們將在下一節中更詳細地介紹其他選項。
3.2 診斷命令行
使用jcmd工具
jcmd 1234 JFR.start duration=100s filename=flight.jfr
在 JDK 11 之前,爲了能夠以這種方式激活 JFR,我們應該使用未鎖定的商業功能啓動應用程序:
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -cp ./out/ com.baeldung.Main
應用程序運行後,我們使用其進程 ID 來執行各種命令,這些命令採用以下格式:
jcmd <pid|MainClass> <command> [parameters]
它的命令如下:
- JFR.start – starts a new JFR recording
- JFR.check – checks running JFR recording(s)
- JFR.stop – stops a specific JFR recording
- JFR.dump – copies contents of a JFR recording to file
每個命令都有一系列參數。例如,JFR.start 命令具有以下參數:
- name – name of the recording; it serves to be able to reference this recording later with other commands
- delay – dimensional parameter for a time delay of recording start, the default value is 0s
- duration – dimensional parameter for a time interval of the duration of the recording; the default value is 0s, which means unlimited
- filename – name of a file that contains the collected data
- maxage – dimensional parameter for the maximum age of collected data; the default value is 0s, which means unlimited
- maxsize – maximum size of buffers for collected data in bytes; the default value is 0, which means no max size
在本節的開頭,我們已經看到了這些參數的使用示例。有關參數的完整列表,我們可能始終參閱 Java 飛行記錄的官方文檔。
儘管 JFR 設計爲在 JVM 和應用程序的性能上儘可能少地佔用空間,但最好通過設置至少一個參數來限制收集數據的最大量:持續時間、最大長度或最大大小。
5.例子
我們的程序把對象插入list直到出現OutOfMemeryError異常。
public static void main(String[] args) {
List<Object> items = new ArrayList<>(1);
try {
while (true){
items.add(new Object());
}
} catch (OutOfMemoryError e){
System.out.println(e.getMessage());
}
assert items.size() > 0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
不用運行電腦,我們能發現一個缺點:這個while循環一直運行將導致CPU 飆高和內存佔用。我們使用JFR來分析一下。
5.2 開始註冊
用以下命令進行編譯:
javac -d out -sourcepath src/main src/main/com/baeldung/flightrecorder/FlightRecorder.java
在 *out/com/baeldung/flightrecorder*
目錄下會發現一個文件 *FlightRecorder.class*
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
-XX:StartFlightRecording=duration=200s,filename=flight.jfr
-cp ./out/ com.baeldung.flightrecorder.FlightRecorder
5.3 可視化
在視圖的左側,我們看到"常規"、內存、代碼和線程等部分。每個部分包含包含詳細信息的各種選項卡。例如,"代碼"部分的選項卡"熱方法"包含方法調用的統計信息:
在此選項卡中,我們可以發現示例程序的另一個缺點:方法 java.util.Array.grow(int)已調用 17 次,以便在每次沒有足夠的空間來添加對象時放大數組容量。
在更現實的程序中,我們可能會看到許多其他有用的信息:
有關已創建對象的統計信息,當它們被垃圾回收器創建和銷燬時
關於線程的年表的詳細報告,當他們被鎖定或活動
應用程序正在執行的 I/O 操作。