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