google-site-verification: google61fe8ba583a51912.html
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