Monitoring JVM and Applications with JDK Flight Recorder

Emrullah YILDIRIM
6 min readDec 20, 2022

--

Java sadece programlama dili olmanın ötesinde aslında çok büyük bir ekosistemi ifade etmektedir. JDK ile birlikte gelen bir çok faydalı tool, processeslerin yaşam döngüsü boyunca profilling ve monitoring için olanak sağlar.

  • Java VisualVM (jvisualvm.exe)
  • JConsole (jconsole.exe)
  • Java Mission Control (jmc.exe)
  • Diagnostic Command Tool (jcmd.exe)

\bin klasorünün altına hiç bakmadı iseniz şimdi bir göz atmanın tam zamanı!

1. Java Flight Recorder Basic Concepts

1.1 JFR nedir?

JDK 7 ile hayatımıza girmiş ve JDK 11 ile open-source olarak hayatına devam eden, run-time’ da çalışan olay bazlı monitoring ve profilling aracı olarak tanımlayabiliriz.

JFR, sadece uygulamanın değil aynı zamanda JVM içindeki olaylarıda (garbage collection) izlememize olanak sağlar. JFR, uygulamanın performansını olabildiğince en az düzeyde etkilemek üzere tasarlanmıştır. Fakat JFR standalone bir uygulama değildir. JMC (Java Mission Control) ve JFR verisini görselleştirmek için geliştirilen pluginler ile birlikte anlam kazanmaktadır.

JFR verisini toplamak ve bu veriyi anlamlı bir şekilde sorunsuz kullanabilmek için yukarıda bahis edilen tüm toolların ve uygulamanızın, aynı Java dağıtımını kullandığından emin olalım.

2. Events and Data Flow

2.1 Events

Eventler, Java run-time ve uygulamanın kendisi tarafından oluşturulur. Uygulamaya yük getirmemesi için Thread Local Buffer’da saklanır ve periyodik olarak JFR tarafından .jfr dosyasına yazılır.

Eventler isme, zamana(timestamp), thread, stack ve heap bilgilerine sahiptir.

JDK 18 ile birlikte 150 farklı event türü vardır. JFR ise 3 farklı event türünde bize olanak sağlar. Bunlar;

  • instant events (anlık olarak gerçekleşince loglanan)
  • duration events (süresi belirli bir eşiği geçince loglanan)
  • sample/requestable events (sistem etkinliğine bağlı loglanan)

JFR, çalışan sistemi son derece yüksek bir ayrıntı düzeyinde izler. Bu muazzam miktarda veri üretir. Oluşan bu ek yükü mümkün olduğu kadar düşük tutmak için, kaydedilen EVENT türlerini gerçekten ihtiyacımız olanlarla sınırlamamız gerekir.

2.2 Read/Write Operations

Bildiğiniz üzere diske yazma işlemleri pahalıdır ve bu nedenle kayıt için etkinleştirdiğiniz event verilerini dikkatli bir şekilde seçerek bu maliyeti en aza indirmeye çalışmalısınız.

JFR, eventlerle ilgili verileri tek bir çıktı dosyasına, flight.jfr dosyasına kaydeder.

Yazma maliyetini en aza indirmek ve uygulama performansını en düşük düzeyde etkilemek için JFR, toplanan dataları diske aktarmadan önce verileri depolamak için çeşitli bufferlar kullanır. Bu nedenle, .jfr dosyasında istenenden daha fazla veri bulabilir veya kronolojik sırada görmeyebiliriz. Fakat JMC’yi kullanırsak, olayları kronolojik sırayla görselleştirdiği için bu gerçeği fark etmeyebiliriz bile.

Yeteri kadar hızlı bir şekilde diske yazılamayan her data atılır. Böyle bir durumda .jfr dosyasında etkilenen zaman dilimi belirtilmektedir.

JFR, istersek verileri hiçbir zaman diske yazılmaması üzerede ayarlanabilir. Böyle bir durumda veriler global-buffer’larda saklanacaktır. Bu bufferların taşması durumunda en eski veri atılacaktır. Bu konu çok daha karmaşık hale gelebileceği için meraklıların kaynaklarda belirtilen Java Platform, Standard Edition Java Flight Recorder Runtime Guide dökümanında ki 1.2. bölümü okumalarını tavsiye ediyorum.

3. Running Java Flight Recorder

3.1 Command Line

JFR ticari bir özellik olduğundan ve yalnızca Java Platformu, Standard Edition (Oracle Java SE Advanced ve Oracle Java SE Suite) tabanlı ticari paketlerde mevcut olduğundan, -XX:+UnlockCommercialFeatures seçeneklerini kullanarak ticari özellikleri de etkinleştirmeniz gerekir.

Varsayılan olarak JFR, JVM’de devre dışıdır. JFR’i etkinleştirmek için Java uygulamanızı -XX:+FlightRecorder seçeneğiyle başlatmanız gerekir.

java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=200s,filename=flight.jfr path-to-class-file

JFR’ı başlatırken bir sürü parametre ile name, delay, maxage, maxsize vb. konfigürasyon yapabilirsiniz. Bu yazıda tüm bu konfigürasyon detaylarına değinmeyeceğim fakat meraklıların yine kaynaklardaki Java Platform, Standard Edition Java Flight Recorder Runtime Guide dökümanındaki 2.3. bölümü ve Monitoring Java Applications with Flight Recorder blogundaki 4.2. bölümü okumalarını tavsiye ediyorum.

Productionda çalışmakta olan uygulamanız için JFR başlatmak isterseniz; cmd-> jps yazıp aktif java process1eri görüntüledikten sonra pid ile “jcmd <pid> filename=recording.jfr dumponexit=true” komunutunu kullanabilirsiniz.

4. Visualize Data

JDK dağıtımının bir parçası olan Java Mission Control’e flight.jfr dosyasını besliyoruz. JMC, verileri hoş şekilde görselleştirmemize yardımcı oluyor.

Ana ekran

Ana ekran, uygulamanın executionı sırasında CPU’yu nasıl kullandığına ilişkin bilgileri bize gösterir. Yukarıdaki resimde bir spring uygulamasının ayağa kalkarken nasıl CPU’yu kullandığını verilen zaman aralığı için izleyebiliriz.

Memory ekranı

Ekranın sol tarafında General, Memory, bölümlerini görüyoruz. Her bölüm ayrıntılı bilgiler içeren çeşitli sekmeler içerir. Memory ekranında ise heap hakkında istatistikleri ve garbage collection hakkında detaylı sekmeler bulunmakta.

Code, Thread ve I/O sekmelerinde ise verilen zaman aralığı için sırasıyla; stack traceleri ve çalışan her bir method için çağrım sayısı, yükü ve geçen zamanı, thread bölümünde ise threadlerin call treelerini, contentionsları ve latency gibi sekmelerini görebiliriz. I/O sekmesinde ise database querylerini ve sürelerini keşfedebiliriz.

4.1 Starting JFR Programmatically with JDK Flight Recorder API

JDK 8u262 ile birlikte gelen JFR API ile birlikte JFR’i programatik olarakta başlatmaya olanak sağlanıyor. Yazabileceğiniz en basit program, JFR’in uygun olmadığını kontrol etmek içindir;

    public static void main(String[] args) throws IOException {
boolean isAvailable = FlightRecorder.isAvailable();
System.err.println(isAvailable);
FlightRecorder.getFlightRecorder().getEventTypes().forEach(p-> System.out.println(p.getName()));
}
"C:\Program Files\Java\jdk-11.0.12\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3\lib\idea_rt.jar=51694:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Users\emrullah.yildirim\Desktop\LocalRepository\Intellij Projects\Training\target\classes" com.emrullah.training.main.Deneme
true
jdk.X509Validation
jdk.X509Certificate
jdk.TLSHandshake
jdk.SecurityPropertyModification
jdk.ActiveRecording
jdk.ActiveSetting
jdk.JavaErrorThrow
jdk.ExceptionStatistics
jdk.JavaExceptionThrow
jdk.SocketWrite
...

Process finished with exit code 0

Aşağıdaki sınıf bir JFR abstractionı oluşturmak için bir örnektir;

import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import jdk.jfr.Configuration;
import jdk.jfr.Recording;

public class LocalJFR {
private Map<Long, Recording> recordings = new HashMap<>();

@Override
public long startRecording() throws Exception {
return startRecording(new Recording(), "jfr-reopenjdk-jfr-2cording");
}

@Override
public File endRecording(long id) throws Exception {
Recording recording = recordings.remove(id);
recording.stop();
recording.close();
return recording.getDestination().toFile();
}
}

Custom bir event tanımlamak için ise en basit şekilde jdk.jfr.Event sınıfını kalıtan bir sınıf ile oluşturabilirsiniz.

4.2 Health Report

Java agents, Java Instrumentation API kullanarak ve byte kodları değiştirerek JVM’de çalışan uygulamaları değiştirebilen özel bir sınıf türüdür. Health Report hem Java agent hemde single-file programdır. JFR tarafından üretilen metrikleri standart-out olarak görüntülememizi sağlamaktadır.

=================== HEALTH REPORT === 2021-05-13 23:57:50 ====================
| GC: G1Old/G1New Phys. memory: 28669 MB Alloc Rate: 8 MB/s |
| OC Count : 28 Initial Heap: 448 MB Total Alloc: 190 MB |
| OC Pause Avg: 40.1 ms Used Heap : 19 MB Thread Count: 20.0 |
| OC Pause Max: 48.8 ms Commit. Heap: 47 MB Class Count : 3894.0 |
| YC Count : 8 CPU Machine : 20.12 % Safepoints: 335 |
| YC Pause Avg: 5.7 ms CPU JVM User : 10.28 % Max Safepoint: 46.4 ms |
| YC Pause Max: 22.4 ms CPU JVM System: 1.07 % Max Comp. Time: 728.3 ms |
|--- Top Allocation Methods ------------------------------- -----------------|
| DataBufferInt.(int) 11.27 % |
| Component.size() 9.01 % |
| BufferedContext.validate(...) 6.21 % |
| Path2D$Double.(...) 5.87 % |
| SunGraphics2D.clone() 5.85 % |
|--- Hot Methods ------------------------------------------------------------|
| DRenderer._endRendering(int, int) 51.11 % |
| DRenderer.copyAARow(...) 6.67 % |
| Arrays.fill(...) 4.44 % |
| StringConcatFactory.doStringConcat(...) 2.22 % |
| MarlinTileGenerator.getAlphaNoRLE(...) 2.22 % |
==============================================================================

Agent olarak health-report çalıştırmak istersek aşağıdaki komutu kullanabiliriz

$ java -javaagent:health-report.jar com.example.MyApplication

Health report JDK 17 ve üzeri sürümlerle çalışmaktadır.

5. Güvenlik

Java Flight Recorder yalnızca teşhis amaçlıdır. JFR dosyası, potansiyel olarak Java komut satırı seçenekleri ve ortam değişkenleri gibi gizli bilgiler içerebilir. Kayıt dosyalarını depolarken veya aktarırken çok dikkatli olun.

Kaynaklar

# Teşekkürler!
* Twitter: @emrllhyildirim

--

--

Emrullah YILDIRIM

Kendine Blogger. Yazarak öğrenen, yazdıklarini paylaşan Java Software Engineer.