google-site-verification: google61fe8ba583a51912.html
Java Flight Recorder SDK: Deep Dive into JVM Telemetry

Java Flight Recorder (JFR) is a powerful profiling and diagnostics tool built into the JDK. The JFR SDK allows developers to create custom events and extend JFR's capabilities for application-specific monitoring.

Understanding JFR Architecture

JFR consists of three main components:

  • Events: Data points captured during application execution
  • Recording: Collection of events over a period of time
  • Repository: Storage for recording files

Basic JFR SDK Usage

Example 1: Basic Custom Events

import jdk.jfr.*; import java.time.Duration; import java.util.concurrent.TimeUnit; // Define a custom JFR event @Name("com.example.OrderProcessingEvent") @Label("Order Processing Event") @Category({"Business", "Order Management"}) @Description("Tracks order processing lifecycle") class OrderProcessingEvent extends Event { @Label("Order ID") public String orderId; @Label("Customer ID") public String customerId; @Label("Processing Time") @Description("Time taken to process order in milliseconds") public long processingTime; @Label("Order Status") public String status; @Label("Items Count") public int itemsCount; @Label("Total Amount") @Amount(Amount.CURRENCY) public double totalAmount; } public class BasicJFRDemo { public static void main(String[] args) throws Exception { // Start a JFR recording Recording recording = new Recording(); recording.setName("BusinessMetrics"); recording.setMaxSize(100 * 1024 * 1024); // 100 MB recording.setMaxAge(Duration.ofMinutes(10)); recording.start(); // Simulate order processing with JFR events for (int i = 1; i <= 100; i++) { processOrder("ORD-" + i, "CUST-" + (i % 10), i * 150.0, i); Thread.sleep(50); } // Stop recording and save to file recording.stop(); recording.dump(Paths.get("business-metrics.jfr")); recording.close(); System.out.println("JFR recording saved to business-metrics.jfr"); } private static void processOrder(String orderId, String customerId, double amount, int itemsCount) { OrderProcessingEvent event = new OrderProcessingEvent(); event.begin(); try { // Simulate order processing long startTime = System.nanoTime(); // Business logic simulation Thread.sleep((long) (Math.random() * 100)); boolean success = Math.random() > 0.1; // 90% success rate event.status = success ? "SUCCESS" : "FAILED"; event.processingTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); event.status = "INTERRUPTED"; } finally { event.orderId = orderId; event.customerId = customerId; event.itemsCount = itemsCount; event.totalAmount = amount; event.commit(); } } }

Example 2: Advanced Event Configuration

import jdk.jfr.*; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // Custom annotation for threshold-based events @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface Threshold { long value() default 1000; // milliseconds } // Timed event with threshold @Name("com.example.DatabaseQueryEvent") @Label("Database Query Execution") @Category({"Database", "Performance"}) @Threshold(500) // 500ms threshold class DatabaseQueryEvent extends Event { @Label("Query SQL") @Description("SQL query being executed") public String sql; @Label("Execution Time") @Description("Query execution time in milliseconds") public long executionTime; @Label("Rows Affected") public int rowsAffected; @Label("Database Name") public String database; @Label("Success") public boolean success; @Label("Error Message") public String errorMessage; } // High-volume event with sampling @Name("com.example.CacheOperationEvent") @Label("Cache Operation") @Category({"Cache", "Performance"}) @StackTrace(false) // Disable stack traces for high-frequency events @Enabled(false) // Disabled by default class CacheOperationEvent extends Event { @Label("Operation Type") public String operation; // GET, PUT, REMOVE @Label("Cache Name") public String cacheName; @Label("Key") public String key; @Label("Execution Time") public long executionTime; @Label("Cache Hit") public boolean hit; } public class AdvancedEventDemo { public static void main(String[] args) throws Exception { // Configure recording with specific settings Configuration config = Configuration.getConfiguration("default"); Recording recording = new Recording(config); recording.setName("AdvancedEventsDemo"); recording.setToDisk(true); recording.setDestination(Paths.get("advanced-events.jfr")); recording.start(); // Enable specific events programmatically recording.enable("com.example.DatabaseQueryEvent") .withStackTrace(); recording.enable("com.example.CacheOperationEvent") .with("threshold", "1000"); // 1ms threshold // Simulate database operations simulateDatabaseOperations(); // Simulate cache operations simulateCacheOperations(); Thread.sleep(5000); recording.stop(); recording.close(); } private static void simulateDatabaseOperations() { String[] queries = { "SELECT * FROM users WHERE id = ?", "UPDATE products SET price = ? WHERE id = ?", "INSERT INTO orders (user_id, total) VALUES (?, ?)", "DELETE FROM sessions WHERE expired_at < NOW()" }; for (int i = 0; i < 50; i++) { String sql = queries[i % queries.length]; executeDatabaseQuery(sql); } } private static void executeDatabaseQuery(String sql) { DatabaseQueryEvent event = new DatabaseQueryEvent(); event.begin(); try { long start = System.nanoTime(); // Simulate database execution Thread.sleep((long) (Math.random() * 1000)); event.executionTime = (System.nanoTime() - start) / 1_000_000; event.sql = sql; event.database = "production-db"; event.rowsAffected = (int) (Math.random() * 1000); event.success = Math.random() > 0.05; // 95% success rate if (!event.success) { event.errorMessage = "Simulated database error"; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); event.success = false; event.errorMessage = "Operation interrupted"; } finally { event.commit(); } } private static void simulateCacheOperations() { String[] operations = {"GET", "PUT", "REMOVE"}; String[] keys = {"user:123", "product:456", "session:789", "config:app"}; for (int i = 0; i < 1000; i++) { String operation = operations[i % operations.length]; String key = keys[i % keys.length]; performCacheOperation(operation, key); } } private static void performCacheOperation(String operation, String key) { CacheOperationEvent event = new CacheOperationEvent(); if (event.isEnabled()) { event.begin(); try { long start = System.nanoTime(); // Simulate cache operation Thread.sleep((long) (Math.random() * 5)); event.executionTime = (System.nanoTime() - start) / 1_000_000; event.operation = operation; event.cacheName = "application-cache"; event.key = key; event.hit = Math.random() > 0.3; // 70% hit rate } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { event.commit(); } } else { // Perform operation without event recording try { Thread.sleep((long) (Math.random() * 5)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }

JFR SDK for Application Monitoring

Example 3: Comprehensive Application Monitoring

import jdk.jfr.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; public class ApplicationMonitoringDemo { private static final AtomicInteger activeRequests = new AtomicInteger(0); private static final AtomicLong totalRequests = new AtomicLong(0); private static final Map<String, AtomicLong> endpointStats = new ConcurrentHashMap<>(); // Request event for HTTP endpoints @Name("com.example.HttpRequestEvent") @Label("HTTP Request") @Category({"Web", "Performance"}) class HttpRequestEvent extends Event { @Label("Request ID") public String requestId; @Label("HTTP Method") public String method; @Label("Endpoint") public String endpoint; @Label("Response Status") public int statusCode; @Label("Processing Time") public long processingTime; @Label("Active Requests") public int concurrentRequests; @Label("User Agent") public String userAgent; @Label("Error Message") public String error; } // Garbage collection monitoring @Name("com.example.GCMonitoringEvent") @Label("Garbage Collection Info") @Category({"JVM", "GC"}) @Period("1 s") // Emit every second class GCMonitoringEvent extends Event { @Label("Used Heap") @Description("Used heap memory in bytes") public long usedHeap; @Label("Max Heap") @Description("Maximum heap memory in bytes") public long maxHeap; @Label("GC Count") @Description("Total GC count") public long gcCount; @Label("GC Time") @Description("Total GC time in milliseconds") public long gcTime; } // Business transaction event @Name("com.example.BusinessTransactionEvent") @Label("Business Transaction") @Category({"Business", "Transaction"}) class BusinessTransactionEvent extends Event { @Label("Transaction ID") public String transactionId; @Label("Transaction Type") public String type; @Label("Start Time") public long startTime; @Label("End Time") public long endTime; @Label("Duration") public long duration; @Label("Success") public boolean success; @Label("Steps Completed") public int stepsCompleted; @Label("Total Steps") public int totalSteps; } private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); private volatile boolean monitoring = true; public void startMonitoring() { // Start JFR recording Recording recording = new Recording(); recording.setName("ApplicationMonitoring"); recording.enable("com.example.HttpRequestEvent"); recording.enable("com.example.GCMonitoringEvent"); recording.enable("com.example.BusinessTransactionEvent"); recording.start(); // Start background monitoring tasks startGCMonitoring(); startRequestSimulation(); // Schedule recording stop scheduler.schedule(() -> { monitoring = false; recording.stop(); try { recording.dump(Paths.get("application-monitoring.jfr")); System.out.println("Monitoring data saved to application-monitoring.jfr"); } catch (Exception e) { e.printStackTrace(); } recording.close(); scheduler.shutdown(); }, 2, TimeUnit.MINUTES); } private void startGCMonitoring() { scheduler.scheduleAtFixedRate(() -> { GCMonitoringEvent event = new GCMonitoringEvent(); if (event.isEnabled()) { Runtime runtime = Runtime.getRuntime(); event.usedHeap = runtime.totalMemory() - runtime.freeMemory(); event.maxHeap = runtime.maxMemory(); // In real application, get GC stats from MXBeans event.gcCount = 0; // Would get from GarbageCollectorMXBean event.gcTime = 0; // Would get from GarbageCollectorMXBean event.commit(); } }, 0, 1, TimeUnit.SECONDS); } private void startRequestSimulation() { for (int i = 0; i < 5; i++) { scheduler.execute(this::simulateRequests); } scheduler.execute(this::simulateBusinessTransactions); } private void simulateRequests() { String[] endpoints = {"/api/users", "/api/products", "/api/orders", "/api/search"}; String[] methods = {"GET", "POST", "PUT", "DELETE"}; String[] userAgents = {"Mozilla/5.0", "Apache-HttpClient", "curl/7.0"}; Random random = new Random(); while (monitoring) { try { String endpoint = endpoints[random.nextInt(endpoints.length)]; String method = methods[random.nextInt(methods.length)]; handleHttpRequest( UUID.randomUUID().toString(), method, endpoint, userAgents[random.nextInt(userAgents.length)] ); Thread.sleep(random.nextInt(500)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } private void handleHttpRequest(String requestId, String method, String endpoint, String userAgent) { HttpRequestEvent event = new HttpRequestEvent(); event.begin(); activeRequests.incrementAndGet(); totalRequests.incrementAndGet(); endpointStats.computeIfAbsent(endpoint, k -> new AtomicLong()).incrementAndGet(); try { long startTime = System.nanoTime(); // Simulate request processing Thread.sleep((long) (Math.random() * 1000)); // Simulate occasional errors boolean success = Math.random() > 0.1; // 90% success rate event.statusCode = success ? 200 : 500; if (!success) { event.error = "Simulated server error"; } event.processingTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); event.statusCode = 503; event.error = "Service unavailable"; } finally { event.requestId = requestId; event.method = method; event.endpoint = endpoint; event.userAgent = userAgent; event.concurrentRequests = activeRequests.get(); event.commit(); activeRequests.decrementAndGet(); } } private void simulateBusinessTransactions() { String[] transactionTypes = {"ORDER_CREATION", "PAYMENT_PROCESSING", "INVENTORY_UPDATE"}; Random random = new Random(); while (monitoring) { try { String transactionType = transactionTypes[random.nextInt(transactionTypes.length)]; processBusinessTransaction( UUID.randomUUID().toString(), transactionType ); Thread.sleep(random.nextInt(2000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } private void processBusinessTransaction(String transactionId, String type) { BusinessTransactionEvent event = new BusinessTransactionEvent(); event.begin(); long startTime = System.currentTimeMillis(); event.startTime = startTime; event.transactionId = transactionId; event.type = type; try { int totalSteps = 3 + (int) (Math.random() * 3); // 3-5 steps event.totalSteps = totalSteps; for (int step = 1; step <= totalSteps; step++) { // Simulate step execution Thread.sleep((long) (Math.random() * 500)); event.stepsCompleted = step; // Simulate occasional step failure if (Math.random() < 0.1) { // 10% chance of failure throw new RuntimeException("Step " + step + " failed"); } } event.success = true; } catch (Exception e) { event.success = false; } finally { long endTime = System.currentTimeMillis(); event.endTime = endTime; event.duration = endTime - startTime; event.commit(); } } public static void main(String[] args) throws Exception { ApplicationMonitoringDemo monitor = new ApplicationMonitoringDemo(); monitor.startMonitoring(); // Keep main thread alive Thread.sleep(130000); // 2 minutes + buffer } }

Advanced JFR SDK Features

Example 4: Dynamic Event Control and Filtering

import jdk.jfr.*; import java.lang.reflect.*; import java.util.*; import java.util.function.*; public class DynamicJFRControl { // Event with dynamic thresholds @Name("com.example.DynamicThresholdEvent") @Label("Dynamic Threshold Monitoring") @Category({"Monitoring", "Dynamic"}) class DynamicThresholdEvent extends Event { @Label("Metric Name") public String metricName; @Label("Metric Value") public double value; @Label("Threshold") public double threshold; @Label("Exceeded") public boolean thresholdExceeded; } private final Map<String, Double> thresholds = new ConcurrentHashMap<>(); private final Map<String, Consumer<Double>> metricHandlers = new ConcurrentHashMap<>(); private final Recording recording; public DynamicJFRControl() { this.recording = new Recording(); setupRecording(); } private void setupRecording() { recording.setName("DynamicMonitoring"); // Enable events with custom settings recording.enable("com.example.DynamicThresholdEvent") .with("threshold", "100.0") .with("period", "1 s"); recording.start(); } public void registerMetric(String metricName, double initialThreshold) { thresholds.put(metricName, initialThreshold); metricHandlers.put(metricName, value -> { double threshold = thresholds.get(metricName); boolean exceeded = value > threshold; if (exceeded) { DynamicThresholdEvent event = new DynamicThresholdEvent(); event.metricName = metricName; event.value = value; event.threshold = threshold; event.thresholdExceeded = true; event.commit(); } }); } public void updateThreshold(String metricName, double newThreshold) { thresholds.put(metricName, newThreshold); System.out.println("Updated threshold for " + metricName + " to " + newThreshold); } public void recordMetric(String metricName, double value) { Consumer<Double> handler = metricHandlers.get(metricName); if (handler != null) { handler.accept(value); } } public void startMetricSimulation() { Thread simulator = new Thread(() -> { Random random = new Random(); String[] metrics = {"CPU_Usage", "Memory_Usage", "Response_Time", "Queue_Size"}; // Initialize metrics for (String metric : metrics) { registerMetric(metric, 80.0); // Default threshold 80% } while (true) { try { for (String metric : metrics) { double value = 50 + random.nextDouble() * 60; // 50-110% recordMetric(metric, value); } // Randomly adjust thresholds if (random.nextDouble() < 0.1) { // 10% chance String randomMetric = metrics[random.nextInt(metrics.length)]; double newThreshold = 70 + random.nextDouble() * 30; // 70-100% updateThreshold(randomMetric, newThreshold); } Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); simulator.setDaemon(true); simulator.start(); } public void stop() { recording.stop(); try { recording.dump(Paths.get("dynamic-monitoring.jfr")); } catch (Exception e) { e.printStackTrace(); } recording.close(); } public static void main(String[] args) throws Exception { DynamicJFRControl controller = new DynamicJFRControl(); controller.startMetricSimulation(); // Run for 30 seconds Thread.sleep(30000); controller.stop(); } }

Example 5: JFR with Custom Annotations and Metadata

import jdk.jfr.*; import java.lang.annotation.*; import java.util.*; // Custom annotations for business monitoring @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface BusinessEvent { String domain(); String subdomain() default ""; Severity severity() default Severity.INFO; } enum Severity { INFO, WARNING, ERROR, CRITICAL } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface SensitiveData { boolean mask() default true; } // Business event with custom annotations @Name("com.example.UserLoginEvent") @Label("User Login Event") @BusinessEvent(domain = "Security", subdomain = "Authentication", severity = Severity.INFO) class UserLoginEvent extends Event { @Label("User ID") public String userId; @Label("Login Time") public long loginTime; @Label("IP Address") public String ipAddress; @Label("User Agent") public String userAgent; @Label("Success") public boolean success; @SensitiveData @Label("Failure Reason") public String failureReason; @Label("Session Duration") public long sessionDuration; } @Name("com.example.PaymentEvent") @Label("Payment Processing Event") @BusinessEvent(domain = "Finance", subdomain = "Payments", severity = Severity.WARNING) class PaymentEvent extends Event { @Label("Payment ID") public String paymentId; @Label("Amount") @Amount(Amount.CURRENCY) public double amount; @Label("Currency") public String currency; @Label("Payment Method") public String paymentMethod; @Label("Status") public String status; @Label("Processing Time") public long processingTime; @SensitiveData(mask = false) // Don't mask for auditing @Label("Customer Email") public String customerEmail; } public class AnnotatedEventsDemo { private final Map<Class<? extends Event>, BusinessEvent> eventMetadata = new HashMap<>(); private final Recording recording; public AnnotatedEventsDemo() { // Collect event metadata eventMetadata.put(UserLoginEvent.class, UserLoginEvent.class.getAnnotation(BusinessEvent.class)); eventMetadata.put(PaymentEvent.class, PaymentEvent.class.getAnnotation(BusinessEvent.class)); this.recording = new Recording(); setupRecording(); } private void setupRecording() { recording.setName("AnnotatedBusinessEvents"); // Configure events based on annotations for (Class<? extends Event> eventClass : eventMetadata.keySet()) { BusinessEvent meta = eventMetadata.get(eventClass); String eventName = eventClass.getAnnotation(Name.class).value(); EventSettings settings = recording.enable(eventName); // Adjust settings based on severity switch (meta.severity()) { case CRITICAL: case ERROR: settings.withStackTrace(); break; case WARNING: settings.with("threshold", "0 ms"); break; } } recording.start(); } public void simulateUserLogins() { String[] users = {"alice", "bob", "charlie", "diana"}; String[] ips = {"192.168.1.1", "10.0.0.1", "172.16.0.1"}; String[] userAgents = { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" }; Random random = new Random(); for (int i = 0; i < 50; i++) { UserLoginEvent event = new UserLoginEvent(); event.begin(); try { String user = users[random.nextInt(users.length)]; event.userId = user; event.loginTime = System.currentTimeMillis(); event.ipAddress = ips[random.nextInt(ips.length)]; event.userAgent = userAgents[random.nextInt(userAgents.length)]; event.success = random.nextDouble() > 0.1; // 90% success rate if (!event.success) { event.failureReason = random.nextBoolean() ? "Invalid credentials" : "Account locked"; } // Simulate login processing Thread.sleep(random.nextInt(200)); if (event.success) { // Simulate session Thread.sleep(random.nextInt(1000)); event.sessionDuration = random.nextInt(3600000); // up to 1 hour } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { event.commit(); } } } public void simulatePayments() { String[] currencies = {"USD", "EUR", "GBP", "JPY"}; String[] methods = {"CREDIT_CARD", "PAYPAL", "BANK_TRANSFER"}; String[] statuses = {"SUCCESS", "FAILED", "PENDING", "DECLINED"}; Random random = new Random(); for (int i = 0; i < 30; i++) { PaymentEvent event = new PaymentEvent(); event.begin(); try { event.paymentId = "PAY-" + UUID.randomUUID().toString().substring(0, 8); event.amount = 10 + random.nextDouble() * 990; // $10-$1000 event.currency = currencies[random.nextInt(currencies.length)]; event.paymentMethod = methods[random.nextInt(methods.length)]; event.status = statuses[random.nextInt(statuses.length)]; event.customerEmail = "customer" + i + "@example.com"; // Simulate payment processing long startTime = System.nanoTime(); Thread.sleep(100 + random.nextInt(400)); event.processingTime = (System.nanoTime() - startTime) / 1_000_000; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { event.commit(); } } } public void stop() { recording.stop(); try { recording.dump(Paths.get("annotated-events.jfr")); // Print event statistics System.out.println("=== Event Statistics ==="); for (Class<? extends Event> eventClass : eventMetadata.keySet()) { BusinessEvent meta = eventMetadata.get(eventClass); System.out.printf("Event: %s, Domain: %s, Severity: %s%n", eventClass.getSimpleName(), meta.domain(), meta.severity()); } } catch (Exception e) { e.printStackTrace(); } recording.close(); } public static void main(String[] args) throws Exception { AnnotatedEventsDemo demo = new AnnotatedEventsDemo(); demo.simulateUserLogins(); demo.simulatePayments(); demo.stop(); } }

Best Practices for JFR SDK

1. Event Design

// Good event design principles @Name("com.example.WellDesignedEvent") @Label("Well Designed Business Event") @Category({"Business", "Monitoring"}) class WellDesignedEvent extends Event { // Use meaningful labels and descriptions @Label("Business Correlation ID") @Description("Unique identifier for business transaction correlation") public String correlationId; // Use appropriate data types @Label("Processing Duration") @Description("Time taken to process in milliseconds") public long duration; // Use enums for fixed sets of values @Label("Operation Status") public OperationStatus status; // Avoid sensitive data or use masking @SensitiveData @Label("User Identifier") public String userId; } enum OperationStatus { SUCCESS, FAILED, TIMEOUT, RETRY }

2. Performance Considerations

class PerformanceAwareEvents { // Check if event is enabled before expensive operations public void performOperation() { ExpensiveOperationEvent event = new ExpensiveOperationEvent(); if (event.isEnabled()) { // Only compute expensive data if event is enabled String expensiveData = computeExpensiveData(); event.data = expensiveData; event.begin(); // ... operation logic event.commit(); } else { // Perform operation without event overhead performOperationLogic(); } } private String computeExpensiveData() { // Expensive computation return "expensive-result"; } private void performOperationLogic() { // Normal operation without event overhead } }

Use Cases for JFR SDK

  1. Application Performance Monitoring: Custom business metrics
  2. Troubleshooting: Detailed event logging for debugging
  3. Capacity Planning: Resource usage patterns
  4. Security Auditing: Authentication and authorization events
  5. Business Analytics: Transaction monitoring and analysis

Conclusion

The Java Flight Recorder SDK provides powerful capabilities for:

  • Custom Event Creation: Tailor monitoring to specific business needs
  • Fine-Grained Control: Dynamic event configuration and filtering
  • Low Overhead: Efficient event collection with minimal performance impact
  • Integration: Seamless integration with existing JFR ecosystem
  • Production Ready: Built-in support in modern JDKs

Key benefits include:

  • Deep Application Insights: Business-level telemetry alongside system metrics
  • Performance Diagnostics: Correlate business events with JVM performance
  • Operational Excellence: Proactive monitoring and alerting
  • Reduced Debugging Time: Detailed event context for troubleshooting

The JFR SDK transforms JFR from a system profiling tool into a comprehensive application observability platform, enabling organizations to gain deep insights into both technical and business aspects of their Java applications.

Leave a Reply

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


Macro Nepal Helper