Java Flight Recorder(JFR)是JVM的診斷和性能分析工具。它能夠收集有關JVM以及在其上運行的Java應用程序的數據。JFR是集成到JVM中的,因此JFR對JVM的性能影響很是小,咱們能夠放心的使用它。java
通常來講,在使用默認配置的時候,性能影響要小於1%。git
JFR的歷史好久遠了。早在Oracle2008年收購BEA的時候就有了。JFR通常和JMC(Java Mission Control)協同工做。程序員
JFR是一個基於事件的低開銷的分析引擎,具備高性能的後端,能夠以二進制格式編寫事件,而JMC是一個GUI工具,用於檢查JFR建立的數據文件。github
這些工具最先是在BEA的JRockit JVM中出現的,最後被移植到了Oracle JDK。最開始JFR是商用版本,可是在JDK11的時候,JFR和JMC徹底開源了,這意味着咱們在非商用的狀況下也可使用了。後端
而在今天的JDK 14中,引入了一個新的JFR特性叫作JFR Event Streaming,咱們將在本文中詳細講解。瀏覽器
先介紹一下JFR和JMC。緩存
更多內容請訪問 www.flydean.com
上面咱們簡單的介紹了一下JFR。JFR是JVM的調優工具,經過不停的收集JVM和java應用程序中的各類事件,從而爲後續的JMC分析提供數據。服務器
Event是由三部分組成的:時間戳,事件名和數據。同時JFR也會處理三種類型的Event:持續一段時間的Event,馬上觸發的Event和抽樣的Event。app
爲了保證性能的最新影響,在使用JFR的時候,請選擇你須要的事件類型。工具
JFR從JVM中搜集到Event以後,會將其寫入一個小的thread-local緩存中,而後刷新到一個全局的內存緩存中,最後將緩存中的數據寫到磁盤中去。
或者你能夠配置JFR不寫到磁盤中去,可是這樣緩存中只會保存部分events的信息。這也是爲何會有JDK14 JEP 349的緣由。
開啓JFR有不少種方式,這裏咱們關注下面兩種:
-XX:StartFlightRecording:<options>
啓動命令行參數的格式如上所述。
JFR能夠獲取超過一百種不一樣類型的元數據。若是要咱們一個個來指定這些元數據,將會是一個很是大的功能。因此JDK已經爲咱們提供了兩個默認的profile:default.jfc and profile.jfc。
其中 default.jfc 是默認的記錄等級,對JVM性能影響不大,適合普通的,大部分應用程序。而profile.jfc包含了更多的細節,對性能影響會更多一些。
若是你不想使用默認的兩個jfc文件,也能夠按照你本身的須要來建立。
下面看一個更加完整的命令行參數:
-XX:StartFlightRecording:disk=true,filename=/tmp/customer.jfr,maxage=5h,settings=profile
上面的命令會建立一個最大age是5h的profile信息文件。
命令行添加參數仍是太麻煩了,若是咱們想動態添加JFR,則可使用jcmd命令。
jcmd <pid> JFR.start name=custProfile settings=default jcmd <pid> JFR.dump filename=custProfile.jfr jcmd <pid> JFR.stop
上面的命令在一個運行中的JVM中啓動了JFR,並將統計結果dump到了文件中。
上面的custProfile.jfr是一個二進制文件,爲了對其進行分析,咱們須要和JFR配套的工具JMC。
JDK Mission Control 是一個用於對 Java 應用程序進行管理、監視、概要分析和故障排除的工具套件。
在JDK14中,JMC是獨立於JDK單獨發行的。咱們能夠下載以後進行安裝。
咱們先啓動一個程序,用於作JFR的測試。
@Slf4j public class ThreadTest { public static void main(String[] args) { ExecutorService executorService= Executors.newFixedThreadPool(10); Runnable runnable= ()->{ while(true){ log.info(Thread.currentThread().getName()); try { Thread.sleep(500); } catch (InterruptedException e) { log.error(e.getMessage(),e); } } }; for(int i=0; i<10; i++){ executorService.submit(runnable); } } }
很簡單的一個程序,啓動了10個線程,咱們啓動這個程序。
而後再去看看JMC的界面:
咱們能夠看到在界面的左邊已經能夠看到運行在本機的ThreadTest程序了。
點擊MBean服務器,能夠看到該java程序的面板信息,裏面包含CPU,堆棧信息。
在下面有7個tab分別是概覽,MBean瀏覽器,觸發器,系統,內存,線程,和診斷命令。
經過下面的tab咱們能夠得到更加詳細的java程序的信息,而且經過觸發器和診斷命令,咱們還能夠對目標java程序的JVM發送命令。
JMC很是強大,也有不少功能,具體的細節你們能夠本身運行去體會。
由於本文主要是將JFR,下面咱們將講解如何在JMC中建立JFR和分析JFR。
上面右側的MBean服務器下就是飛行記錄器了,也就是咱們的目標。
點擊飛行記錄器:
咱們就能夠開始建立一個JFR了。
目標文件就是JFR的生成地址,名稱能夠本身隨便起一個,記錄時間表示須要記錄多長時間範圍以內的JFR。
點下一步:
這一步能夠選擇更加詳細的JVM參數。
點下一步:
這裏,咱們能夠選擇須要監控的Profile事件選項。能夠按照你的須要進行選擇。
最後點完成建立JFR。
上面咱們的JFR記錄了1分鐘的Profile,在1分鐘以後,咱們能夠看到目標JFR文件生成了。
生成完JFR以後,JMC會自動打開生成的JFR文件,咱們獲得一個大綱視圖。
裏面包含java應用程序,JVM內部,環境和事件瀏覽器。
事件瀏覽器中列出了咱們在1分鐘以內監控的事件。
JMC瀏覽器不只能夠監控本機的應用程序,也能夠監控遠程的應用程序。因爲JMC的鏈接是經過JMX協議,因此遠程java程序須要開啓JMX協議的支持。
JMC好用是好用,可是要一個一個的去監聽JFR文件會很繁瑣。接下來咱們來介紹一下怎麼採用寫代碼的方式來監聽JFR事件。
仍是上面的圖,若是咱們想經過程序來獲取「Class Loading Statistics"的信息,能夠這樣作。
上圖的右側是具體的信息,咱們能夠看到主要包含三個字段:開始時間,Loaded Class Count和 Unloaded Class Count。
咱們的思路就是使用jdk.jfr.consumer.RecordingFile去讀取生成的JFR文件,而後對文件中的數據進行解析。
相應代碼以下:
@Slf4j public class JFREvent { private static Predicate<RecordedEvent> testMaker(String s) { return e -> e.getEventType().getName().startsWith(s); } private static final Map<Predicate<RecordedEvent>, Function<RecordedEvent, Map<String, String>>> mappers = Map.of(testMaker("jdk.ClassLoadingStatistics"), ev -> Map.of("start", ""+ ev.getStartTime(), "Loaded Class Count",""+ ev.getLong("loadedClassCount"), "Unloaded Class Count", ""+ ev.getLong("unloadedClassCount") )); @Test public void readJFRFile() throws IOException { RecordingFile recordingFile = new RecordingFile(Paths.get("/Users/flydean/flight_recording_1401comflydeaneventstreamThreadTest21710.jfr")); while (recordingFile.hasMoreEvents()) { var event = recordingFile.readEvent(); if (event != null) { var details = convertEvent(event); if (details == null) { // details爲空 } else { // 打印目標 log.info("{}",details); } } } } public Map<String, String> convertEvent(final RecordedEvent e) { for (var ent : mappers.entrySet()) { if (ent.getKey().test(e)) { return ent.getValue().apply(e); } } return null; } }
注意,在convertEvent方法中,咱們將從文件中讀取的Event轉換成了map對象。
在構建map時,咱們先判斷Event的名字是否是咱們所須要的jdk.ClassLoadingStatistics,而後將Event中其餘的字段進行轉換。最後輸出。
運行結果:
{start=2020-04-29T02:18:41.770618136Z, Loaded Class Count=2861, Unloaded Class Count=0} ...
能夠看到輸出結果和界面上面是同樣的。
講了這麼多,終於到咱們今天要講的內容了:JFR事件流。
上面的JFR事件中,咱們須要去讀取JFR文件,進行分析。可是文件是死的,人是活的,每次分析都須要先生成JFR文件簡直是太複雜了。是個程序員都不能容忍。
在JFR事件流中,咱們能夠監聽Event的變化,從而在程序中進行相應的處理。這樣不須要生成JFR文件也能夠監聽事件變化。
public static void main(String[] args) throws IOException, ParseException { //default or profile 兩個默認的profiling configuration files Configuration config = Configuration.getConfiguration("default"); try (var es = new RecordingStream(config)) { es.onEvent("jdk.GarbageCollection", System.out::println); es.onEvent("jdk.CPULoad", System.out::println); es.onEvent("jdk.JVMInformation", System.out::println); es.setMaxAge(Duration.ofSeconds(10)); es.start(); } }
看看上面的例子。咱們經過Configuration.getConfiguration("default")獲取到了默認的default配置。
而後經過構建了default的RecordingStream。經過onEvent
方法,咱們對相應的Event進行處理。
本文講解了JFR,JMC和JDK14的最新特性JFR event stream。但願可以對你們在工做中有所幫助。
本文的例子https://github.com/ddean2009/learn-java-base-9-to-20
本文做者:flydean程序那些事本文連接:http://www.flydean.com/jdk14-jfr-jmc-event-stream/
本文來源:flydean的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!