Java Flight Recorder (JFR) Usage in Java: Complete Performance Monitoring Guide

Java Flight Recorder (JFR) is a powerful profiling and diagnostics tool built into the JDK that collects detailed runtime information with minimal performance overhead.


1. JFR Overview and Architecture

JFR Architecture

┌─────────────────────────────────────────────────┐
│               Java Application                  │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│             JFR Infrastructure                  │
│  ┌─────────────┐  ┌─────────────┐              │
│  │   Events    │  │  Recording  │              │
│  │   Engine    │  │   Buffer    │              │
│  └─────────────┘  └─────────────┘              │
│  ┌─────────────┐  ┌─────────────┐              │
│  │   Event     │  │   Data      │              │
│  │  Producers  │  │  Repository │              │
│  └─────────────┘  └─────────────┘              │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│               JFR File (.jfr)                  │
└─────────────────────────────────────────────────┘

Key Features:

  • Low Overhead: Typically 1-2% performance impact
  • Continuous Monitoring: Always-on in production
  • Detailed Events: 100+ event types covering all JVM aspects
  • JDK Integration: Built into Oracle JDK and OpenJDK
  • Security: Secure data collection with access controls

2. Enabling and Starting JFR

JVM Arguments for JFR

public class JFREnablement {
public static void main(String[] args) throws Exception {
demonstrateJFROptions();
checkJFRStatus();
}
public static void demonstrateJFROptions() {
System.out.println("=== JFR Enablement Options ===");
System.out.println("""
Commercial Features (JDK 8-10):
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder
JDK 11+ (JFR Open Source):
No unlock required, always available
Basic Recording:
-XX:StartFlightRecording=filename=recording.jfr,dumponexit=true
Continuous Recording:
-XX:StartFlightRecording=filename=recording.jfr,maxsize=100MB,maxage=1h
Custom Settings:
-XX:StartFlightRecording=settings=profile,filename=recording.jfr
""");
}
public static void checkJFRStatus() {
System.out.println("\n=== JFR Status Check ===");
try {
// Check if JFR is available
Class<?> jfrClass = Class.forName("jdk.jfr.FlightRecorder");
System.out.println("JFR is available in this JVM");
// Get FlightRecorder instance
Object flightRecorder = jfrClass.getMethod("getFlightRecorder").invoke(null);
boolean isInitialized = (boolean) jfrClass.getMethod("isInitialized").invoke(flightRecorder);
System.out.println("JFR initialized: " + isInitialized);
} catch (ClassNotFoundException e) {
System.out.println("JFR not available in this JVM");
} catch (Exception e) {
System.err.println("Error checking JFR status: " + e.getMessage());
}
}
}

Programmatic JFR Control

import jdk.jfr.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class ProgrammaticJFRControl {
public static void main(String[] args) throws Exception {
demonstrateProgrammaticControl();
createCustomRecording();
demonstrateEventConfiguration();
}
public static void demonstrateProgrammaticControl() {
System.out.println("=== Programmatic JFR Control ===");
try {
// Get the FlightRecorder instance
FlightRecorder flightRecorder = FlightRecorder.getFlightRecorder();
// List all available recordings
System.out.println("Active Recordings:");
for (Recording recording : flightRecorder.getRecordings()) {
System.out.println("  - " + recording.getName() + 
" (State: " + recording.getState() + ")");
}
// Create a new recording
Recording recording = new Recording();
recording.setName("Programmatic-Recording");
recording.setDestination(Paths.get("programmatic-recording.jfr"));
recording.setMaxSize(100 * 1024 * 1024); // 100MB
recording.setMaxAge(Duration.ofHours(1));
// Start recording
recording.start();
System.out.println("Started recording: " + recording.getName());
// Do some work while recording...
performWorkload();
// Stop and close recording
recording.stop();
recording.close();
System.out.println("Recording completed and saved");
} catch (Exception e) {
System.err.println("JFR operation failed: " + e.getMessage());
e.printStackTrace();
}
}
public static void createCustomRecording() throws Exception {
System.out.println("\n=== Custom Recording Configuration ===");
Recording recording = new Recording();
// Basic configuration
recording.setName("Custom-Configuration-Recording");
recording.setDestination(Paths.get("custom-recording.jfr"));
// Size and age limits
recording.setMaxSize(50 * 1024 * 1024); // 50MB
recording.setMaxAge(Duration.ofMinutes(30));
// Use specific settings profile
recording.setSettings(Configuration.getConfiguration("profile"));
// Enable specific events
recording.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
recording.enable("jdk.GarbageCollection").withThreshold(Duration.ofMillis(10));
// Start recording
recording.start();
System.out.println("Custom recording started with profile settings");
// Run for a short time
Thread.sleep(5000);
// Stop and dump
recording.stop();
recording.dump(Paths.get("custom-dump.jfr"));
recording.close();
System.out.println("Custom recording completed");
}
public static void demonstrateEventConfiguration() {
System.out.println("\n=== Event Configuration ===");
try (Recording recording = new Recording()) {
// Configure specific events with custom settings
recording.enable("jdk.ObjectAllocationSample")
.with("throttle", "150/s");
recording.enable("jdk.ActiveSetting")
.withPeriod(Duration.ofSeconds(5));
recording.enable("jdk.JavaMonitorWait")
.withThreshold(Duration.ofMillis(100));
recording.setName("Event-Config-Recording");
recording.start();
System.out.println("Event-configured recording started");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void performWorkload() {
System.out.println("Performing workload during recording...");
// Simulate some work that will generate JFR events
for (int i = 0; i < 1000; i++) {
// Allocate some objects
String data = "Workload data " + i;
byte[] buffer = new byte[1024];
// Some CPU work
Math.pow(i, 2);
// Occasional sleep
if (i % 100 == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}

3. Custom JFR Events

Creating Custom Events

import jdk.jfr.*;
import java.util.concurrent.ThreadLocalRandom;
public class CustomEventsExample {
public static void main(String[] args) throws Exception {
demonstrateCustomEvents();
demonstrateAdvancedEvents();
demonstrateEventTiming();
}
@Name("com.example.OrderProcessing")
@Label("Order Processing Event")
@Category("Business")
@Description("Records order processing operations")
public static class OrderProcessingEvent extends Event {
@Label("Order ID")
public String orderId;
@Label("Customer ID")
public String customerId;
@Label("Order Amount")
@Amount("USD")
public double amount;
@Label("Processing Time")
@Timespan(Timespan.MILLISECONDS)
public long processingTime;
@Label("Success")
public boolean success;
@Label("Error Message")
public String errorMessage;
}
@Name("com.example.CacheOperation")
@Label("Cache Operation Event")
@Category("Performance")
public static class CacheOperationEvent extends Event {
@Label("Operation Type")
public String operationType; // GET, PUT, REMOVE
@Label("Cache Key")
public String key;
@Label("Cache Hit")
public boolean hit;
@Label("Response Time")
@Timespan(Timespan.MICROSECONDS)
public long responseTime;
}
public static void demonstrateCustomEvents() {
System.out.println("=== Custom JFR Events ===");
try (Recording recording = new Recording()) {
recording.setName("Custom-Events-Recording");
recording.enable("com.example.*"); // Enable custom events
recording.start();
// Simulate order processing with custom events
for (int i = 0; i < 50; i++) {
processOrder("ORD-" + i, "CUST-" + (i % 10), 100.0 + i * 10);
}
// Simulate cache operations
for (int i = 0; i < 100; i++) {
performCacheOperation("key-" + i);
}
recording.stop();
recording.dump(Paths.get("custom-events.jfr"));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Custom events recording completed");
}
private static void processOrder(String orderId, String customerId, double amount) {
OrderProcessingEvent event = new OrderProcessingEvent();
event.begin();
try {
// Simulate order processing work
Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100));
// 90% success rate
boolean success = ThreadLocalRandom.current().nextDouble() < 0.9;
event.orderId = orderId;
event.customerId = customerId;
event.amount = amount;
event.success = success;
if (!success) {
event.errorMessage = "Payment processing failed";
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
event.success = false;
event.errorMessage = "Processing interrupted";
} finally {
event.end();
event.commit();
}
}
private static void performCacheOperation(String key) {
CacheOperationEvent event = new CacheOperationEvent();
event.begin();
try {
// Simulate cache operation
String[] operations = {"GET", "PUT", "REMOVE"};
String operation = operations[ThreadLocalRandom.current().nextInt(operations.length)];
event.operationType = operation;
event.key = key;
event.hit = ThreadLocalRandom.current().nextBoolean();
// Simulate response time
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 5));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
event.end();
event.commit();
}
}
// Advanced event with stack traces and thresholds
@Name("com.example.SlowOperation")
@Label("Slow Operation Event")
@Category("Performance")
@StackTrace(true)
@Threshold(value = "10 ms")
public static class SlowOperationEvent extends Event {
@Label("Operation Name")
public String operationName;
@Label("Duration")
@Timespan(Timespan.MILLISECONDS)
public long duration;
@Label("Input Size")
public int inputSize;
}
public static void demonstrateAdvancedEvents() {
System.out.println("\n=== Advanced Event Features ===");
// Events with stack traces and thresholds
for (int i = 0; i < 20; i++) {
performSlowOperation("Operation-" + i, i * 100);
}
}
private static void performSlowOperation(String name, int inputSize) {
SlowOperationEvent event = new SlowOperationEvent();
try {
// Simulate slow operation
long sleepTime = ThreadLocalRandom.current().nextInt(5, 20);
Thread.sleep(sleepTime);
event.operationName = name;
event.duration = sleepTime;
event.inputSize = inputSize;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
event.commit();
}
}
// Timed event with automatic timing
public static void demonstrateEventTiming() {
System.out.println("\n=== Event Timing ===");
@Name("com.example.TimedOperation")
@Label("Timed Operation Event")
class TimedOperationEvent extends Event {
@Label("Iteration")
public int iteration;
@Label("Result")
public String result;
}
try (Recording recording = new Recording()) {
recording.start();
for (int i = 0; i < 10; i++) {
TimedOperationEvent event = new TimedOperationEvent();
event.iteration = i;
// Use try-with-resources for automatic timing
try (EventContext context = event.begin()) {
// Simulate work
Thread.sleep(ThreadLocalRandom.current().nextInt(10, 50));
event.result = "Success-" + i;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
event.result = "Interrupted";
}
event.commit();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

4. JFR Event Types and Categories

Built-in Event Categories

import jdk.jfr.*;
import java.lang.management.*;
import java.util.*;
public class JFREventCategories {
public static void main(String[] args) throws Exception {
demonstrateEventCategories();
demonstrateCPUEvents();
demonstrateMemoryEvents();
demonstrateIOEvents();
}
public static void demonstrateEventCategories() {
System.out.println("=== JFR Event Categories ===");
System.out.println("""
Core Event Categories:
JVM:
- jdk.JVMInformation
- jdk.JVMSystemProperties
- jdk.SystemProcess
CPU:
- jdk.CPULoad
- jdk.CPUInformation
- jdk.ThreadCPULoad
Memory:
- jdk.GCHeapSummary
- jdk.GarbageCollection
- jdk.ObjectAllocationSample
- jdk.ObjectAllocationInNewTLAB
Threads:
- jdk.ThreadStart
- jdk.ThreadEnd
- jdk.JavaMonitorWait
- jdk.ThreadPark
I/O:
- jdk.FileRead
- jdk.FileWrite
- jdk.SocketRead
- jdk.SocketWrite
Exceptions:
- jdk.JavaExceptionThrow
- jdk.JavaErrorThrow
""");
}
public static void demonstrateCPUEvents() throws Exception {
System.out.println("\n=== CPU Events Demonstration ===");
try (Recording recording = new Recording()) {
recording.setName("CPU-Events-Recording");
// Enable CPU-related events
recording.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(2));
recording.enable("jdk.ThreadCPULoad").withPeriod(Duration.ofSeconds(3));
recording.enable("jdk.CPUInformation");
recording.start();
// Generate CPU load
generateCPULoad();
Thread.sleep(10000); // Record for 10 seconds
recording.stop();
recording.dump(Paths.get("cpu-events.jfr"));
}
System.out.println("CPU events recording completed");
}
private static void generateCPULoad() {
Thread cpuLoadThread = new Thread(() -> {
long endTime = System.currentTimeMillis() + 8000;
while (System.currentTimeMillis() < endTime) {
// Generate some CPU load
for (int i = 0; i < 100000; i++) {
Math.sqrt(i) * Math.log(i + 1);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
cpuLoadThread.start();
try {
cpuLoadThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void demonstrateMemoryEvents() throws Exception {
System.out.println("\n=== Memory Events Demonstration ===");
try (Recording recording = new Recording()) {
recording.setName("Memory-Events-Recording");
// Enable memory-related events
recording.enable("jdk.GarbageCollection");
recording.enable("jdk.GCHeapSummary").withPeriod(Duration.ofSeconds(2));
recording.enable("jdk.ObjectAllocationSample").with("throttle", "100/s");
recording.enable("jdk.ObjectAllocationInNewTLAB").with("throttle", "50/s");
recording.start();
// Generate memory allocation patterns
generateMemoryAllocations();
Thread.sleep(8000);
System.gc(); // Trigger GC for events
recording.stop();
recording.dump(Paths.get("memory-events.jfr"));
}
System.out.println("Memory events recording completed");
}
private static void generateMemoryAllocations() {
List<Object> objects = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10000; i++) {
// Allocate objects of different sizes
if (random.nextBoolean()) {
objects.add(new byte[1024]); // 1KB
} else {
objects.add(new byte[10240]); // 10KB
}
// Occasionally clear to trigger GC
if (i % 1000 == 0) {
objects.clear();
}
}
}
public static void demonstrateIOEvents() throws Exception {
System.out.println("\n=== I/O Events Demonstration ===");
try (Recording recording = new Recording()) {
recording.setName("IO-Events-Recording");
// Enable I/O events
recording.enable("jdk.FileRead");
recording.enable("jdk.FileWrite");
recording.enable("jdk.SocketRead");
recording.enable("jdk.SocketWrite");
recording.start();
// Generate file I/O
generateFileIO();
Thread.sleep(5000);
recording.stop();
recording.dump(Paths.get("io-events.jfr"));
}
System.out.println("I/O events recording completed");
}
private static void generateFileIO() {
try {
// Create temporary files for I/O operations
java.nio.file.Path tempFile = java.nio.file.Files.createTempFile("jfr-demo", ".txt");
// Write data
for (int i = 0; i < 100; i++) {
String data = "Line " + i + ": " + java.time.Instant.now() + "\n";
java.nio.file.Files.writeString(tempFile, data, 
java.nio.file.StandardOpenOption.APPEND);
}
// Read data
List<String> lines = java.nio.file.Files.readAllLines(tempFile);
// Clean up
java.nio.file.Files.deleteIfExists(tempFile);
} catch (Exception e) {
System.err.println("File I/O generation failed: " + e.getMessage());
}
}
}

5. JFR Analysis and Tools

JFR Analysis Programmatically

import jdk.jfr.consumer.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.*;
public class JFRAnalysis {
public static void main(String[] args) throws Exception {
analyzeRecordingFile();
demonstrateEventQueries();
generateAnalysisReport();
}
public static void analyzeRecordingFile() throws Exception {
System.out.println("=== JFR Recording Analysis ===");
Path recordingPath = Paths.get("recording.jfr");
// Check if recording file exists, create one if not
if (!java.nio.file.Files.exists(recordingPath)) {
createSampleRecording(recordingPath);
}
try (RecordingFile recordingFile = new RecordingFile(recordingPath)) {
System.out.println("Recording file size: " + 
java.nio.file.Files.size(recordingPath) + " bytes");
// Read all events
Map<String, Long> eventCounts = new HashMap<>();
List<RecordedEvent> allEvents = new ArrayList<>();
while (recordingFile.hasMoreEvents()) {
RecordedEvent event = recordingFile.readEvent();
allEvents.add(event);
String eventType = event.getEventType().getName();
eventCounts.merge(eventType, 1L, Long::sum);
}
System.out.println("\nEvent Statistics:");
eventCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry -> 
System.out.printf("  %s: %,d events%n", entry.getKey(), entry.getValue()));
analyzeSpecificEvents(allEvents);
}
}
private static void createSampleRecording(Path path) throws Exception {
System.out.println("Creating sample recording...");
try (Recording recording = new Recording()) {
recording.setName("Sample-Recording");
recording.setDestination(path);
recording.start();
// Generate some events
for (int i = 0; i < 1000; i++) {
String data = "Sample data " + i;
byte[] buffer = new byte[1024];
Thread.sleep(1);
}
recording.stop();
}
}
private static void analyzeSpecificEvents(List<RecordedEvent> events) {
System.out.println("\n=== Specific Event Analysis ===");
// Analyze garbage collection events
List<RecordedEvent> gcEvents = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.GarbageCollection"))
.toList();
System.out.println("Garbage Collection Events: " + gcEvents.size());
if (!gcEvents.isEmpty()) {
long totalGCTime = gcEvents.stream()
.mapToLong(e -> e.getDuration().toMillis())
.sum();
System.out.printf("Total GC time: %,d ms%n", totalGCTime);
System.out.printf("Average GC time: %.2f ms%n", 
(double) totalGCTime / gcEvents.size());
}
// Analyze allocation events
List<RecordedEvent> allocationEvents = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.ObjectAllocationSample"))
.toList();
System.out.println("Allocation Sample Events: " + allocationEvents.size());
// Analyze exception events
List<RecordedEvent> exceptionEvents = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.JavaExceptionThrow"))
.toList();
System.out.println("Exception Events: " + exceptionEvents.size());
if (!exceptionEvents.isEmpty()) {
Map<String, Long> exceptionCounts = new HashMap<>();
for (RecordedEvent event : exceptionEvents) {
String exceptionClass = event.getString("throwableClass");
exceptionCounts.merge(exceptionClass, 1L, Long::sum);
}
System.out.println("Exception Types:");
exceptionCounts.forEach((className, count) -> 
System.out.printf("  %s: %,d%n", className, count));
}
}
public static void demonstrateEventQueries() throws Exception {
System.out.println("\n=== Event Query Examples ===");
Path recordingPath = Paths.get("recording.jfr");
try (RecordingFile recordingFile = new RecordingFile(recordingPath)) {
// Query for high CPU methods
System.out.println("Methods with high CPU usage:");
recordingFile.readEvents()
.filter(e -> e.getEventType().getName().equals("jdk.ExecutionSample"))
.limit(10)
.forEach(event -> {
String method = event.getString("method");
System.out.println("  " + method);
});
// Query for slow I/O operations
System.out.println("\nSlow I/O operations:");
recordingFile.readEvents()
.filter(e -> e.getEventType().getName().equals("jdk.FileRead") || 
e.getEventType().getName().equals("jdk.FileWrite"))
.filter(e -> e.getDuration().toMillis() > 10)
.limit(5)
.forEach(event -> {
String operation = event.getEventType().getName();
long duration = event.getDuration().toMillis();
String file = event.getString("path");
System.out.printf("  %s: %s (%d ms)%n", operation, file, duration);
});
}
}
public static void generateAnalysisReport() throws Exception {
System.out.println("\n=== JFR Analysis Report ===");
Path recordingPath = Paths.get("recording.jfr");
try (RecordingFile recordingFile = new RecordingFile(recordingPath)) {
// Collect statistics
JFRReport report = new JFRReport();
while (recordingFile.hasMoreEvents()) {
RecordedEvent event = recordingFile.readEvent();
report.processEvent(event);
}
report.printSummary();
}
}
static class JFRReport {
private long startTime = Long.MAX_VALUE;
private long endTime = Long.MIN_VALUE;
private final Map<String, Long> eventCounts = new HashMap<>();
private final Map<String, Long> gcTimes = new HashMap<>();
private final List<RecordedEvent> exceptions = new ArrayList<>();
private long totalAllocatedBytes = 0;
public void processEvent(RecordedEvent event) {
String eventType = event.getEventType().getName();
eventCounts.merge(eventType, 1L, Long::sum);
// Track time range
long eventTime = event.getStartTime().toEpochMilli();
startTime = Math.min(startTime, eventTime);
endTime = Math.max(endTime, eventTime);
// Process specific event types
switch (eventType) {
case "jdk.GarbageCollection":
String gcName = event.getString("name");
long gcDuration = event.getDuration().toMillis();
gcTimes.merge(gcName, gcDuration, Long::sum);
break;
case "jdk.ObjectAllocationInNewTLAB":
totalAllocatedBytes += event.getLong("tlabSize");
break;
case "jdk.JavaExceptionThrow":
exceptions.add(event);
break;
}
}
public void printSummary() {
System.out.println("=== JFR Analysis Summary ===");
long duration = endTime - startTime;
System.out.printf("Recording duration: %,d ms%n", duration);
System.out.printf("Total events: %,d%n", 
eventCounts.values().stream().mapToLong(Long::longValue).sum());
System.out.println("\nTop Event Types:");
eventCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(8)
.forEach(entry -> 
System.out.printf("  %-40s: %,6d%n", entry.getKey(), entry.getValue()));
if (!gcTimes.isEmpty()) {
System.out.println("\nGarbage Collection:");
gcTimes.forEach((name, time) -> 
System.out.printf("  %-20s: %,6d ms%n", name, time));
}
System.out.printf("%nTotal allocated (TLAB): %,d bytes%n", totalAllocatedBytes);
System.out.printf("Exceptions thrown: %,d%n", exceptions.size());
}
}
}

6. Production JFR Usage

Continuous Monitoring in Production

import jdk.jfr.*;
import java.nio.file.*;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ProductionJFRMonitoring {
private static final ScheduledExecutorService scheduler = 
Executors.newScheduledThreadPool(2);
private static Recording continuousRecording;
public static void main(String[] args) throws Exception {
startContinuousMonitoring();
demonstrateRollingRecordings();
addShutdownHook();
// Keep application running
Thread.sleep(300000); // 5 minutes
stopMonitoring();
}
public static void startContinuousMonitoring() {
System.out.println("=== Starting Continuous JFR Monitoring ===");
try {
continuousRecording = new Recording();
// Production configuration
continuousRecording.setName("Production-Monitoring");
continuousRecording.setToDisk(true);
continuousRecording.setDestination(Paths.get("production-monitoring.jfr"));
continuousRecording.setMaxSize(500 * 1024 * 1024); // 500MB
continuousRecording.setMaxAge(Duration.ofHours(6)); // Keep 6 hours
continuousRecording.setDumpOnExit(true);
// Use production settings (lower overhead)
continuousRecording.setSettings(Configuration.getConfiguration("default"));
// Start recording
continuousRecording.start();
System.out.println("Continuous monitoring started");
System.out.println("Max size: 500MB, Max age: 6 hours");
} catch (Exception e) {
System.err.println("Failed to start continuous monitoring: " + e.getMessage());
e.printStackTrace();
}
}
public static void demonstrateRollingRecordings() {
System.out.println("\n=== Rolling Recordings ===");
// Schedule periodic recording dumps
scheduler.scheduleAtFixedRate(() -> {
try {
dumpCurrentRecording();
} catch (Exception e) {
System.err.println("Failed to dump recording: " + e.getMessage());
}
}, 5, 5, TimeUnit.MINUTES); // Dump every 5 minutes
System.out.println("Scheduled rolling dumps every 5 minutes");
}
private static void dumpCurrentRecording() {
if (continuousRecording != null && 
continuousRecording.getState() == Recording.State.RUNNING) {
try {
String timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
.toString().replace(":", "-");
Path dumpPath = Paths.get("recording-dump-" + timestamp + ".jfr");
continuousRecording.dump(dumpPath);
System.out.println("Recording dumped to: " + dumpPath.getFileName());
} catch (Exception e) {
System.err.println("Dump failed: " + e.getMessage());
}
}
}
public static class ProductionEvent extends Event {
@Label("Service Name")
public String serviceName;
@Label("Operation Type")
public String operationType;
@Label("Response Time")
@Timespan(Timespan.MILLISECONDS)
public long responseTime;
@Label("Success")
public boolean success;
@Label("Error Code")
public String errorCode;
}
public static void logProductionEvent(String service, String operation, 
long startTime, boolean success, String errorCode) {
ProductionEvent event = new ProductionEvent();
event.serviceName = service;
event.operationType = operation;
event.responseTime = System.currentTimeMillis() - startTime;
event.success = success;
event.errorCode = errorCode;
event.commit();
}
public static void simulateProductionWorkload() {
System.out.println("Simulating production workload...");
scheduler.scheduleAtFixedRate(() -> {
long startTime = System.currentTimeMillis();
try {
// Simulate various operations
String[] services = {"OrderService", "PaymentService", "UserService", "InventoryService"};
String[] operations = {"process", "validate", "update", "query"};
String service = services[ThreadLocalRandom.current().nextInt(services.length)];
String operation = operations[ThreadLocalRandom.current().nextInt(operations.length)];
// Simulate work
Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100));
// 95% success rate
boolean success = ThreadLocalRandom.current().nextDouble() < 0.95;
String errorCode = success ? null : "ERR_" + ThreadLocalRandom.current().nextInt(1000);
// Log event
logProductionEvent(service, operation, startTime, success, errorCode);
if (!success) {
System.out.printf("Operation failed: %s.%s - %s%n", 
service, operation, errorCode);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, 0, 100, TimeUnit.MILLISECONDS); // 10 operations per second
}
public static void addShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown hook: Stopping JFR monitoring...");
stopMonitoring();
}));
}
public static void stopMonitoring() {
System.out.println("Stopping JFR monitoring...");
if (continuousRecording != null) {
try {
continuousRecording.stop();
continuousRecording.close();
System.out.println("Continuous monitoring stopped");
} catch (Exception e) {
System.err.println("Error stopping recording: " + e.getMessage());
}
}
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

7. JFR with Spring Boot Applications

Spring Boot JFR Configuration

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import java.nio.file.Paths;
import java.time.Duration;
@SpringBootApplication
@EnableScheduling
public class SpringBootJFRApplication {
private static Recording jfrRecording;
public static void main(String[] args) {
// Start JFR before Spring context
startJFRRecording();
SpringApplication app = new SpringApplication(SpringBootJFRApplication.class);
app.run(args);
}
@Bean
public JFRMonitoringService jfrMonitoringService() {
return new JFRMonitoringService();
}
private static void startJFRRecording() {
try {
jfrRecording = new Recording();
jfrRecording.setName("SpringBoot-App");
jfrRecording.setDestination(Paths.get("springboot-recording.jfr"));
jfrRecording.setMaxSize(100 * 1024 * 1024); // 100MB
jfrRecording.setMaxAge(Duration.ofHours(1));
jfrRecording.start();
System.out.println("JFR recording started for Spring Boot application");
// Add shutdown hook to stop recording
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (jfrRecording != null) {
jfrRecording.stop();
jfrRecording.close();
System.out.println("JFR recording stopped");
}
}));
} catch (Exception e) {
System.err.println("Failed to start JFR recording: " + e.getMessage());
}
}
}
// Service for JFR monitoring in Spring
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Category;
import org.springframework.stereotype.Service;
import org.springframework.scheduling.annotation.Scheduled;
@Service
class JFRMonitoringService {
@Scheduled(fixedRate = 30000) // Every 30 seconds
public void recordApplicationMetrics() {
ApplicationMetricsEvent event = new ApplicationMetricsEvent();
event.activeRequests = getActiveRequestCount();
event.memoryUsed = getMemoryUsage();
event.databaseConnections = getActiveDatabaseConnections();
event.commit();
}
@Label("Application Metrics")
@Category("Spring Boot")
static class ApplicationMetricsEvent extends Event {
@Label("Active Requests")
int activeRequests;
@Label("Memory Used (MB)")
long memoryUsed;
@Label("Database Connections")
int databaseConnections;
}
private int getActiveRequestCount() {
// Implementation depends on your web framework
return (int) (Math.random() * 100);
}
private long getMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
}
private int getActiveDatabaseConnections() {
// Implementation depends on your data source
return (int) (Math.random() * 20);
}
}
// JFR Controller for dynamic control
import org.springframework.web.bind.annotation.*;
import jdk.jfr.FlightRecorder;
import jdk.jfr.Recording;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/jfr")
class JFRController {
private Recording dynamicRecording;
@PostMapping("/start")
public Map<String, String> startRecording(@RequestParam(defaultValue = "60") int durationSeconds) {
Map<String, String> response = new HashMap<>();
try {
if (dynamicRecording != null && 
dynamicRecording.getState() == Recording.State.RUNNING) {
response.put("status", "error");
response.put("message", "Recording already running");
return response;
}
dynamicRecording = new Recording();
dynamicRecording.setName("Dynamic-Recording");
dynamicRecording.setDuration(Duration.ofSeconds(durationSeconds));
dynamicRecording.start();
response.put("status", "success");
response.put("message", "Recording started for " + durationSeconds + " seconds");
} catch (Exception e) {
response.put("status", "error");
response.put("message", "Failed to start recording: " + e.getMessage());
}
return response;
}
@PostMapping("/stop")
public Map<String, String> stopRecording() {
Map<String, String> response = new HashMap<>();
try {
if (dynamicRecording != null && 
dynamicRecording.getState() == Recording.State.RUNNING) {
dynamicRecording.stop();
dynamicRecording.close();
response.put("status", "success");
response.put("message", "Recording stopped and saved");
} else {
response.put("status", "error");
response.put("message", "No active recording found");
}
} catch (Exception e) {
response.put("status", "error");
response.put("message", "Failed to stop recording: " + e.getMessage());
}
return response;
}
@GetMapping("/status")
public Map<String, Object> getJFRStatus() {
Map<String, Object> status = new HashMap<>();
try {
FlightRecorder flightRecorder = FlightRecorder.getFlightRecorder();
status.put("available", true);
status.put("initialized", flightRecorder.isInitialized());
if (dynamicRecording != null) {
status.put("dynamicRecording", dynamicRecording.getState().name());
} else {
status.put("dynamicRecording", "NOT_RUNNING");
}
} catch (Exception e) {
status.put("available", false);
status.put("error", e.getMessage());
}
return status;
}
}

Conclusion

JFR Usage Summary:

ScenarioConfigurationBenefits
Development Profiling-XX:StartFlightRecording=settings=profileDetailed performance analysis
Production MonitoringContinuous recording with size/age limitsAlways-available diagnostics
Performance DebuggingCustom events with stack tracesRoot cause analysis
Capacity PlanningLong-term recording with specific eventsTrend analysis and planning

Best Practices:

  1. Start Small: Begin with default settings, then customize based on needs
  2. Monitor Overhead: Keep event rates reasonable for production use
  3. Use Appropriate Settings: profile for development, default for production
  4. Implement Cleanup: Manage recording file growth with size/age limits
  5. Secure Access: Control who can start/stop recordings in production
  6. Correlate with Logs: Use JFR events alongside application logs

Key JVM Options:

# Basic recording
-XX:StartFlightRecording=filename=recording.jfr,dumponexit=true
# Production continuous recording  
-XX:StartFlightRecording=filename=recording.jfr,maxsize=500M,maxage=6h
# Custom settings
-XX:StartFlightRecording=settings=default,filename=recording.jfr
# Delayed start
-XX:StartFlightRecording=delay=5m,filename=recording.jfr

Java Flight Recorder provides unprecedented visibility into JVM and application behavior with minimal performance impact, making it an essential tool for both development and production environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper