JVM 性能調(diào)優(yōu)之通過 JProfile 和 JFR 分析系統(tǒng)瓶頸提升系統(tǒng)性能

生成 jfr
JDK飛行記錄器(JFR)是一種結(jié)構(gòu)化日志記錄工具, 它記錄廣泛的系統(tǒng)級(system-level)事件。類似于飛機上的黑盒子,它會持續(xù)記錄飛行數(shù)據(jù),用于調(diào)查飛行事故。JFR會持續(xù)記錄JVM中的 一系列事件,用于診斷問題。這種方式的優(yōu)勢是,它會按時間順序,捕獲導致事故的,詳細系統(tǒng)信息。JFR被設計的,對于性能影響很小,所以 可以安全地在生產(chǎn)環(huán)境長時間運行。
優(yōu)勢:
- 對于運行系統(tǒng)的影響比較小,額外占用資源小于 1%
- 生成的文件比較小,通常生成 10 多分鐘的文件往往小于 1G
查詢 java 進程
通過 jps 命令查詢。

通過 ps -ef | grep java 查詢。
生成 jfr 文件
生成飛行記錄,使用 JFR.start 該實用程序的診斷命令 jcmd。
jcmd 84743 JFR.start duratinotallow=5m settings=profile filename=~/jfr/xxkk.jfrJProfile 介紹
JProfiler是一個用于分析運行JVM內(nèi)部情況的專業(yè)工具。在開發(fā)中你可以使用它,用于質(zhì)量保證,也可以解決你的生產(chǎn)系統(tǒng)遇到的問題。
JProfiler處理四個主要問題:
- 方法調(diào)用這通常被稱為"CPU分析"。方法調(diào)用可以通過不同的方式進行測量和可視化, 分析方法調(diào)用可以幫助了解你的應用程序正在做什么,并找到提高其性能的方法。
- 分配分析堆上對象的分配、引用鏈和垃圾回收屬于"內(nèi)存分析"的范疇。這個功能可以讓你解決內(nèi)存泄漏,總之使用更少的內(nèi)存,分配更少的臨時對象。
- 線程和鎖線程可以持有鎖,例如通過在一個對象上做同步。當多個線程協(xié)作時,可能會出現(xiàn)死鎖,JProfiler可以為你可視化這種情況。此外,鎖可能被爭用,這意味著線程在獲得鎖之前必須等待。通過JProfiler可以深入了解線程及其各種鎖情形。
- 高層子系統(tǒng)許多性能問題發(fā)生在更高的語義層面。例如,對于JDBC調(diào)用,你可能想找出哪條SQL語句是最慢的。對于這樣的子系統(tǒng),JProfiler提供了"探針",將特定有效載荷附加到調(diào)用樹。
JProfiler的UI是一個桌面應用程序。你可以以交互的方式實時分析JVM,也可以在不使用UI的情況下,自動化分析。保存在快照中的分析數(shù)據(jù),可以通過JProfiler UI打開。此外,命令行工具和構(gòu)建工具集成可以幫助你自動分析會話。
注意:JProfile 是商業(yè)軟件,希望大家在使用的過程中購買正版授權(quán)
內(nèi)存分析
記錄的對象
內(nèi)存分析中,可以通過記錄的對象找到最耗費內(nèi)存的對象。只有總對象大小超過固定閾值(通常是堆的1%)的類才會被記錄。默認情況下,JFR中禁用了這個功能,因為它會引入大量的開銷。
下面兩個字段的解釋:
- **實例計數(shù)示例, **可以看到某個用例的堆上還剩下哪些對象(實際上會小于總大小除以每個對象的平均大?。?。
- 預估總大小,這個是一個預估值,預估在開始 jfr 記錄,到 jfr 記錄結(jié)束這個類的實例總大小

https://docs.oracle.com/en/java/javase/17/docs/specs/man/jfr.html。
分配熱點
分配熱點視圖與分配調(diào)用樹一起,允許你直接關(guān)注負責創(chuàng)建所選類的方法。就像記錄的對象視圖,分配熱點視圖也支持標記當前狀態(tài)和觀察一段時間內(nèi)的差值。視圖中會添加一個差值列,它顯示了熱點自當_標記當前值_操作被調(diào)用后的變化。因為默認情況下,分配視圖不會定期更新,所以你必須單擊_計算_工具欄按鈕以獲得一個新數(shù)據(jù)集然后與基線值比較。
計算熱點:

熱點分析:

熱點的分配類:

找到主要是因為 byte[] 數(shù)組分配,成為一個分配熱點方法。
測試代碼
/**
* VM ARG : -Xms64m -Xmx64m
*/
public static void main(String[] args) throws InterruptedException {
List<byte[]> list = new ArrayList<>(1000);
//2kb * 10 * 120 = 2400kb
//數(shù)量 = 10 * 120 / 10 = 120
for (int i = 0; i < 10000; i++) {
Thread.sleep(100);
byte[] arr = new byte[1024 * 2];
list.add(arr);
}
}CPU 分析
調(diào)用樹
跟蹤所有的方法調(diào)用及其調(diào)用棧會消耗相當大的內(nèi)存,短時間內(nèi)就會耗盡所有內(nèi)存。另外,在一個繁忙的JVM中,很難直觀獲得方法調(diào)用的數(shù)量。通常情況下,這個數(shù)字是如此之大,以至于定位和跟隨跟蹤是不可能的。
另一個方面,只有將收集到的數(shù)據(jù)進行匯總,許多性能問題才會變得清晰。這樣,你就可以知道在某個時間段內(nèi),方法調(diào)用相對于整個活動的重要性。如果是單一的跟蹤,你對你所看的數(shù)據(jù)的相對重要性沒有概念。
這就是為什么JProfiler建立了一個所有觀察到的調(diào)用堆棧的累積樹,并注解有觀察到的時間和調(diào)用次數(shù)。時間順序信息被消除,只保留總數(shù)。樹中的每個節(jié)點代表一個至少被觀察過一次的調(diào)用堆棧。節(jié)點的子節(jié)點代表在該調(diào)用堆棧中看到的所有傳出調(diào)用。

調(diào)用樹是"CPU視圖"部分的第一個視圖,當你開始進行CPU分析時,它是一個很好的起點, 因為遵循方法調(diào)用從起點到最細化的終點的自上而下視圖,最容易理解。JProfiler按照子節(jié)點的總時間進行排序,所以你可以深度優(yōu)先打開樹,分析對性能影響最大的部分。

熱點
如果你的應用程序運行得太慢,你要找到那些占用大部分時間的方法。通過調(diào)用樹,有時可以直接找到這些方法, 但通常這樣做是行不通的,因為調(diào)用樹可能很大而且有大量葉節(jié)點
在這種情況下,你需要反轉(zhuǎn)調(diào)用樹:一個所有方法的列表,按其總的自身時間排序,從所有不同的調(diào)用堆棧中累計出來, 并通過回溯跟蹤顯示這些方法是如何被調(diào)用的。在熱點樹中,葉節(jié)點是入口點, 就像應用程序的main 方法或線程的run 方法。從熱點樹中最深的節(jié)點開始,調(diào)用向上傳遞到頂層節(jié)點。

回溯跟蹤中的調(diào)用次數(shù)和執(zhí)行時間并不是指該方法節(jié)點,而是指頂層熱點節(jié)點在這條路徑上被調(diào)用的次數(shù)。理解這一點很重要:粗略一看,你會認為看到的節(jié)點上的信息是該節(jié)點的調(diào)用次數(shù)。然而,在熱點樹中,該信息顯示的是該節(jié)點對頂層節(jié)點的貢獻。所以,你必須這樣理解這些數(shù)字: 沿著這個倒置的調(diào)用堆棧,頂層熱點被調(diào)用了n 次,總持續(xù)時間為t 秒。

通過這里可以看到這個 CASE 是因為正則導致的 CPU 熱點。
測試代碼
static String pattern = " ^([\\u4e00-\\u9fa5]+)((·[\\u4e00-\\u9fa5]+)+|([\\u4e00-\\u9fa5]+))$";
static String defaultName = "張三·無論其是看都看吶阿斯頓啊·薩肯薩肯打開你發(fā)都看啊看你發(fā)個卡看那可能發(fā)看那個可能看吶";
public static void main(String[] args) throws InterruptedException {
int time = 1;
String result;
while (true) {
if (time > 0) {
Thread.sleep(time);
}
result = defaultName.matches(pattern) ? "姓名合法" : "姓名不合法";
}
}參考文檔
- jcmd 指令詳解:https://docs.oracle.com/javacomponents/jmc-5-5/jfr-command-reference/diagnostic-command-reference.htm。
- 生成 jfr 文件:https://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/run.htm。
- jprofile 中文手冊:https://www.ej-technologies.com/resources/jprofiler/v/13.0/help_zh_CN/doc/main/memory.html。





















