JVM應(yīng)用性能剖析工具
前言
我們工作中經(jīng)常面臨下面一系列的問(wèn)題:
- 不清楚應(yīng)用的運(yùn)行情況,包括但不限于GC情況、熱點(diǎn)代碼、熱點(diǎn)線程、異常(特別是被吞沒(méi)的異常);
- 不清楚應(yīng)用代碼執(zhí)行情況,甚至不知道某個(gè)模塊是否還可能會(huì)運(yùn)行,尤其是對(duì)于有著一定歷史的應(yīng)用,可能已經(jīng)有很多開(kāi)發(fā)在上面貢獻(xiàn)過(guò)代碼,但隨著業(yè)務(wù)發(fā)展這些功能可能已經(jīng)不再被使用;
- 不清楚應(yīng)用的IO情況,注意,這里說(shuō)的絕對(duì)不僅僅是zabbix上的"磁盤(pán)IO",而是具體到某個(gè)文件、某個(gè)端口、某個(gè)線程的IO情況。
這些問(wèn)題導(dǎo)致的直接后果就是:對(duì)于性能優(yōu)化,無(wú)從下手,只能憑經(jīng)驗(yàn)和直覺(jué)去猜測(cè)、埋點(diǎn)、優(yōu)化,當(dāng)然,最終結(jié)果一般就是“加機(jī)器擴(kuò)容”。
那么有沒(méi)有一些工具能夠精確定位我我們的問(wèn)題呢?首先我們看看java 有哪些監(jiān)控工具。
Java 監(jiān)控工具
Java 不僅是一種編程語(yǔ)言,而且是一個(gè)非常豐富的生態(tài)系統(tǒng),其中包含許多工具。JDK 包含的程序允許我們編譯自己的程序,并在程序執(zhí)行的整個(gè)生命周期中監(jiān)視它們的狀態(tài)和 Java 虛擬機(jī)的狀態(tài)。
JDK 發(fā)行版的bin文件夾包含以下可用于分析和監(jiān)控的程序:
- Java VisualVM (jvisualvm.exe)
- JConsole (jconsole.exe)
- Java 任務(wù)控制(jmc.exe)
- 診斷命令工具(jcmd.exe)
Java VisualVM 過(guò)去是 Oracle 和 Open JDK 發(fā)行版的一部分。但是,從 Java 9 開(kāi)始,JDK 發(fā)行版不再附帶 Java VisualVM。
但是值得高興的是JDK7以上已經(jīng)內(nèi)置了一款新型的性能剖析工具,也就是今天要重點(diǎn)介紹的-Java Flight Recorder,簡(jiǎn)稱JFR。
Java Flight Recorder 及其基本概念
Java Flight Recorder (JFR) 是一種監(jiān)視工具,用于在 Java 應(yīng)用程序執(zhí)行期間收集有關(guān) Java 虛擬機(jī) (JVM) 中事件的信息。JFR 是 JDK 發(fā)行版的一部分,它已集成到 JVM 中。
JFR旨在盡可能少地影響正在運(yùn)行的應(yīng)用程序的性能。
為了使用JFR,我們應(yīng)該激活它。我們可以通過(guò)兩種方式實(shí)現(xiàn)這一點(diǎn):
啟動(dòng) Java 應(yīng)用程序時(shí)
當(dāng) Java 應(yīng)用程序已經(jīng)在運(yùn)行時(shí)傳遞jcmd工具的診斷命令
JFR 沒(méi)有獨(dú)立的工具。我們使用 Java Mission Control (JMC),它包含一個(gè)插件,允許我們將 JFR 收集的數(shù)據(jù)可視化。
這三個(gè)組件 — JFR、jcmd和JMC — 形成一個(gè)完整的套件,用于收集正在運(yùn)行的 Java 程序的低級(jí)運(yùn)行時(shí)信息。我們可能會(huì)發(fā)現(xiàn)這些信息在優(yōu)化程序時(shí)或在出現(xiàn)問(wèn)題時(shí)診斷程序時(shí)非常有用。
JFR 記錄了關(guān)于 Java 運(yùn)行時(shí)及運(yùn)行在其內(nèi)的 Java 應(yīng)用程序的詳細(xì)信息,記錄用少量的開(kāi)銷完成。數(shù)據(jù)是作為時(shí)間上的數(shù)據(jù)點(diǎn)(稱為事件)記錄的。典型的事件可以是線程等待鎖、GC、CPU 周期使用數(shù)據(jù)等。
在創(chuàng)建飛行記錄時(shí),可以選擇哪些事件應(yīng)當(dāng)保存,這叫做記錄模板。有些模板只保存基本事件,對(duì)性能幾乎沒(méi)有影響。其他模板可能有輕微的性能開(kāi)銷,還可能觸發(fā) GC 來(lái)收集更多信息。通常,超過(guò)百分之幾的開(kāi)銷是很罕見(jiàn)。飛行記錄可用于調(diào)試很大范圍的問(wèn)題,從性能問(wèn)題到內(nèi)存泄漏或嚴(yán)重的鎖競(jìng)爭(zhēng)。
需要注意的是,JFR是一個(gè)性能數(shù)據(jù)采集工具,它把最終采集到的數(shù)據(jù)存在一個(gè)格式為jfr的文件中,由于本身并不具備數(shù)據(jù)分析或者可視化功能,所以一般我們需要配合JDK另外一個(gè)內(nèi)置工具,即JMC一起使用。
什么是JMC
JMC, 即Java任務(wù)控制(Java Mission Control)是從Java7(7u40)和 Java8 的商業(yè)版本包括一項(xiàng)新的監(jiān)控和控制特性的圖形化性能監(jiān)控工具,它可以直接打開(kāi)由jfr生成的原始數(shù)據(jù)采集文件。
可以在自己機(jī)器的命令行輸入 jmc 來(lái)體驗(yàn)。
相比其它Profile工具比有什么優(yōu)點(diǎn)
在開(kāi)發(fā)環(huán)境中,我們用VisualVM、JProfiler等,功能強(qiáng)大,支持圖形化界面操作,可以很快定位代碼問(wèn)題。
但是他們對(duì)應(yīng)用性能的影響也非常大,所以不適合在生產(chǎn)環(huán)境下使用。還有這些軟件要attach到j(luò)vm進(jìn)程上,生產(chǎn)環(huán)境一般網(wǎng)絡(luò)隔離,很難做到。
在生產(chǎn)環(huán)境我們最常用的profiling工具就是java/bin下的jstack,多做幾次jstack,也相當(dāng)于profiling了。
jstack方便易用,但并不是特別適合來(lái)做profiling,操作頻率低,會(huì)導(dǎo)致safepoint指標(biāo)急劇增長(zhǎng)等等。
重點(diǎn)來(lái)了,使用jfr不需要在現(xiàn)有應(yīng)用上額外添加任何參數(shù)、重啟進(jìn)程等,直接在命令行執(zhí)行即可實(shí)時(shí)生效,100%無(wú)入侵,穩(wěn)定可靠不影響線上應(yīng)用運(yùn)行。
使用方式
- 選定一臺(tái)機(jī)器,登陸上去,并找到要監(jiān)控的進(jìn)程PID
- 執(zhí)行下列命令
- pid=`jcmd | grep java進(jìn)程關(guān)鍵詞 | awk '{print $1}'`
- jcmd $pid VM.unlock_commercial_features #先解鎖技能
- jcmd $pid JFR.start name=myrec settings=profile delay=20s duration=2m filename=/tmp/$pid.jfr
- #其中,delay參數(shù)表示profile延遲啟動(dòng)時(shí)間,duration表示持續(xù)采集時(shí)間,這里設(shè)置為2分鐘
- #settings表示使用哪種采集配置
- #官方默認(rèn)自帶一個(gè)名為profile的配置,這個(gè)配置不會(huì)收集異常信息
- #注意,采集數(shù)據(jù)生成后請(qǐng)執(zhí)行下列命令移除這個(gè)采集
- jcmd $pid JFR.stop name=myrec
- 這樣就好了,等待2分鐘+20秒,/tmp/pid.jfr文件就生成好了,這個(gè)文件直接導(dǎo)入JMC工具即可
如何定制采集信息
默認(rèn)采集配置收集的信息較少,性能較好,若需要收集更多信息,請(qǐng)使用JMC工具的模版管理器來(lái)進(jìn)行自定義,
生成好的自定義文件放置在
實(shí)戰(zhàn) OkHttpClient 內(nèi)存溢出問(wèn)題
以最近排查的一個(gè)問(wèn)題為例,應(yīng)用服務(wù)在使用 OkHttpClient 時(shí),在創(chuàng)建大量對(duì)外連接時(shí)線程堆積導(dǎo)致內(nèi)存溢出。
我們首先登錄線上服務(wù)器,通過(guò)上面介紹的設(shè)置,進(jìn)行信息采集:
等待2-3分鐘,在tmp目錄下查看目標(biāo)文件,文件寫(xiě)入完成后,將生成的jfr文件上傳到ftp,通過(guò)安全的方式下載到安裝了JMC 的機(jī)器。
打開(kāi)JMC,加載jfr 文件,開(kāi)始分析。
通過(guò)上圖分析,看見(jiàn)了大量OkHttpClient創(chuàng)建的線程,棧上分配的內(nèi)存也比較多,經(jīng)查詢?cè)创a分析出主要原因是應(yīng)用中在創(chuàng)建 OkHttpClient 對(duì)象時(shí),沒(méi)有創(chuàng)建同一個(gè) OkHttpClient 實(shí)例并重復(fù)使用,而是對(duì)于所有的 http 請(qǐng)求都重復(fù)創(chuàng)建一個(gè)新的實(shí)例,而每個(gè)實(shí)例都有自己的連接池和線程池,從而導(dǎo)致線程大量堆積。
本文轉(zhuǎn)載自微信公眾號(hào)「小汪哥寫(xiě)代碼」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系小汪哥寫(xiě)代碼公眾號(hào)。