1. 程式人生 > 程式設計 >Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

簡介

Java Flight Recorder(JFR)是JVM的診斷和效能分析工具。它可以收集有關JVM以及在其上執行的Java應用程式的資料。JFR是整合到JVM中的,所以JFR對JVM的效能影響非常小,我們可以放心的使用它。

一般來說,在使用預設配置的時候,效能影響要小於1%。

JFR的歷史很久遠了。早在Oracle2008年收購BEA的時候就有了。JFR一般和JMC(Java Mission Control)協同工作。

JFR是一個基於事件的低開銷的分析引擎,具有高效能的後端,可以以二進位制格式編寫事件,而JMC是一個GUI工具,用於檢查JFR建立的資料檔案。

這些工具最早是在BEA的JRockit JVM中出現的,最後被移植到了Oracle JDK。最開始JFR是商用版本,但是在JDK11的時候,JFR和JMC完全開源了,這意味著我們在非商用的情況下也可以使用了。

而在今天的JDK 14中,引入了一個新的JFR特性叫做JFR Event Streaming,我們將在本文中詳細講解。

先介紹一下JFR和JMC。

JFR

上面我們簡單的介紹了一下JFR。JFR是JVM的調優工具,通過不停的收集JVM和java應用程式中的各種事件,從而為後續的JMC分析提供資料。

Event是由三部分組成的:時間戳,事件名和資料。同時JFR也會處理三種類型的Event:持續一段時間的Event,立刻觸發的Event和抽樣的Event。

為了保證效能的最新影響,在使用JFR的時候,請選擇你需要的事件型別。

JFR從JVM中搜集到Event之後,會將其寫入一個小的thread-local快取中,然後重新整理到一個全域性的記憶體快取中,最後將快取中的資料寫到磁碟中去。

或者你可以配置JFR不寫到磁碟中去,但是這樣快取中只會儲存部分events的資訊。這也是為什麼會有JDK14 JEP 349的原因。

開啟JFR有很多種方式,這裡我們關注下面兩種:

1.新增命令列引數

-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資訊檔案。

1.使用jcmd

命令列新增引數還是太麻煩了,如果我們想動態新增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。

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的介面:

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

我們可以看到在介面的左邊已經可以看到執行在本機的ThreadTest程式了。

點選MBean伺服器,可以看到該java程式的面板資訊,裡面包含CPU,堆疊資訊。

在下面有7個tab分別是概覽,MBean瀏覽器,觸發器,系統,記憶體,執行緒,和診斷命令。

通過下面的tab我們可以獲得更加詳細的java程式的資訊,並且通過觸發器和診斷命令,我們還可以對目標java程式的JVM傳送命令。

JMC非常強大,也有很多功能,具體的細節大家可以自己執行去體會。

因為本文主要是將JFR,下面我們將講解如何在JMC中建立JFR和分析JFR。

建立JFR

上面右側的MBean伺服器下就是飛行記錄器了,也就是我們的目標。

點選飛行記錄器:

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

我們就可以開始建立一個JFR了。

目標檔案就是JFR的生成地址,名稱可以自己隨便起一個,記錄時間表示需要記錄多長時間範圍之內的JFR。

點下一步:

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

這一步可以選擇更加詳細的JVM引數。

點下一步:

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

這裡,我們可以選擇需要監控的Profile事件選項。可以按照你的需要進行選擇。

最後點完成建立JFR。

分析JFR

上面我們的JFR記錄了1分鐘的Profile,在1分鐘之後,我們可以看到目標JFR檔案生成了。

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

生成完JFR之後,JMC會自動開啟生成的JFR檔案,我們得到一個大綱檢視。

裡面包含java應用程式,JVM內部,環境和事件瀏覽器。

事件瀏覽器中列出了我們在1分鐘之內監控的事件。

Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)

JMC瀏覽器不僅可以監控本機的應用程式,也可以監控遠端的應用程式。由於JMC的連線是通過JMX協議,所以遠端java程式需要開啟JMX協議的支援。

JFR事件

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檔案簡直是太複雜了。是個程式設計師都不能容忍。

在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

到此這篇關於Java中JDK14的新特性之JFR,JMC和JFR事件流(推薦)的文章就介紹到這了,更多相關JDK14新特性JFR,JMC和JFR事件流內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!