JMC Flight Recorder Analysis in Java

Introduction to Java Flight Recorder (JFR)

Java Flight Recorder is a profiling and event collection framework built into the Java Virtual Machine (JVM). It provides detailed information about JVM and application behavior with minimal performance overhead.

Setting Up JFR Analysis

Enabling JFR and Basic Recording

import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
public class JFRBasicSetup {
public static void main(String[] args) throws Exception {
// Start JFR recording programmatically
startJFRRecording();
// Run your application code
runApplicationWorkload();
// Stop recording and analyze
analyzeJFRRecording();
}
public static void startJFRRecording() {
// Method 1: Using JCMD (requires JFR enabled)
String recordingName = "my-recording-" + 
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
try {
// Start recording using jcmd
ProcessBuilder pb = new ProcessBuilder(
"jcmd", 
String.valueOf(ProcessHandle.current().pid()),
"JFR.start", 
"name=" + recordingName,
"duration=60s",
"filename=recording.jfr"
);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode == 0) {
System.out.println("JFR recording started: " + recordingName);
} else {
System.err.println("Failed to start JFR recording");
}
} catch (Exception e) {
System.err.println("Error starting JFR: " + e.getMessage());
}
}
public static void runApplicationWorkload() {
// Simulate application workload
System.out.println("Running application workload...");
// Create memory pressure
List<byte[]> memoryList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
memoryList.add(new byte[1024 * 1024]); // 1MB each
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Create CPU load
IntStream.range(0, 1000000)
.parallel()
.map(i -> i * i)
.sum();
}
public static void analyzeJFRRecording() {
// Analysis will be done in subsequent examples
System.out.println("Recording completed. Ready for analysis.");
}
}

Programmatic JFR Analysis

Parsing JFR Files with JDK APIs

import jdk.jfr.*;
import jdk.jfr.consumer.*;
import java.io.IOException;
import java.nio.file.*;
import java.time.*;
import java.util.*;
import java.util.stream.*;
public class JFRAnalyzer {
public static void analyzeRecording(String recordingPath) throws IOException {
Path jfrFile = Paths.get(recordingPath);
try (RecordingFile recording = new RecordingFile(jfrFile)) {
System.out.println("=== JFR Recording Analysis ===");
System.out.println("File: " + recordingPath);
System.out.println();
// Analyze different event types
analyzeGCEvents(recording);
analyzeCPUEvents(recording);
analyzeMemoryEvents(recording);
analyzeMethodProfiling(recording);
analyzeExceptions(recording);
analyzeIOEvents(recording);
}
}
private static void analyzeGCEvents(RecordingFile recording) throws IOException {
System.out.println("=== Garbage Collection Analysis ===");
List<RecordedEvent> gcEvents = recording.readEvents()
.filter(event -> event.getEventType().getName().contains("GarbageCollection"))
.collect(Collectors.toList());
if (gcEvents.isEmpty()) {
System.out.println("No GC events found");
return;
}
long totalGCTime = 0;
long maxGCTime = 0;
int youngGCCount = 0;
int oldGCCount = 0;
for (RecordedEvent event : gcEvents) {
String gcName = event.getString("name");
long gcDuration = event.getLong("duration");
totalGCTime += gcDuration;
maxGCTime = Math.max(maxGCTime, gcDuration);
if (gcName.contains("Young")) {
youngGCCount++;
} else if (gcName.contains("Old") || gcName.contains("Full")) {
oldGCCount++;
}
System.out.printf("GC: %s, Duration: %.3f ms%n", 
gcName, gcDuration / 1_000_000.0);
}
System.out.printf("Total GC Time: %.3f ms%n", totalGCTime / 1_000_000.0);
System.out.printf("Max GC Pause: %.3f ms%n", maxGCTime / 1_000_000.0);
System.out.printf("Young GC Count: %d%n", youngGCCount);
System.out.printf("Old GC Count: %d%n", oldGCCount);
System.out.println();
}
private static void analyzeCPUEvents(RecordingFile recording) throws IOException {
System.out.println("=== CPU Analysis ===");
// Analyze execution samples
List<RecordedEvent> executionSamples = recording.readEvents()
.filter(event -> event.getEventType().getName().equals("jdk.ExecutionSample"))
.collect(Collectors.toList());
if (executionSamples.isEmpty()) {
System.out.println("No CPU execution samples found");
return;
}
// Count methods by frequency
Map<String, Long> methodCounts = executionSamples.stream()
.map(event -> {
RecordedFrame frame = event.getStackTrace().getFrames().get(0);
return frame.getMethod().getType().getName() + "." + frame.getMethod().getName();
})
.collect(Collectors.groupingBy(method -> method, Collectors.counting()));
System.out.println("Top CPU-consuming methods:");
methodCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry -> 
System.out.printf("  %s: %d samples%n", entry.getKey(), entry.getValue()));
System.out.println();
}
private static void analyzeMemoryEvents(RecordingFile recording) throws IOException {
System.out.println("=== Memory Analysis ===");
// Analyze allocation events
List<RecordedEvent> allocationEvents = recording.readEvents()
.filter(event -> event.getEventType().getName().equals("jdk.ObjectAllocationInNewTLAB"))
.collect(Collectors.toList());
if (allocationEvents.isEmpty()) {
System.out.println("No memory allocation events found");
return;
}
// Group allocations by class
Map<String, AllocationStats> allocationStats = new HashMap<>();
for (RecordedEvent event : allocationEvents) {
String className = event.getClass("objectClass").getName();
long allocationSize = event.getLong("allocationSize");
allocationStats
.computeIfAbsent(className, k -> new AllocationStats())
.addAllocation(allocationSize);
}
System.out.println("Top allocating classes:");
allocationStats.entrySet().stream()
.sorted((e1, e2) -> Long.compare(e2.getValue().totalSize, e1.getValue().totalSize))
.limit(10)
.forEach(entry -> {
AllocationStats stats = entry.getValue();
System.out.printf("  %s: %d allocations, %.2f MB total%n",
entry.getKey(), stats.count, stats.totalSize / (1024.0 * 1024.0));
});
System.out.println();
}
private static void analyzeMethodProfiling(RecordingFile recording) throws IOException {
System.out.println("=== Method Profiling ===");
List<RecordedEvent> methodProfilingEvents = recording.readEvents()
.filter(event -> event.getEventType().getName().equals("jdk.ExecutionSample"))
.collect(Collectors.toList());
if (methodProfilingEvents.isEmpty()) {
System.out.println("No method profiling events found");
return;
}
// Build call tree
CallTree callTree = new CallTree();
for (RecordedEvent event : methodProfilingEvents) {
RecordedStackTrace stackTrace = event.getStackTrace();
if (stackTrace != null) {
callTree.addStackTrace(stackTrace);
}
}
System.out.println("Hot methods call tree:");
callTree.printTopMethods(5);
System.out.println();
}
private static void analyzeExceptions(RecordingFile recording) throws IOException {
System.out.println("=== Exception Analysis ===");
List<RecordedEvent> exceptionEvents = recording.readEvents()
.filter(event -> event.getEventType().getName().equals("jdk.ExceptionThrown"))
.collect(Collectors.toList());
if (exceptionEvents.isEmpty()) {
System.out.println("No exception events found");
return;
}
Map<String, Integer> exceptionCounts = new HashMap<>();
for (RecordedEvent event : exceptionEvents) {
String exceptionType = event.getClass("thrownClass").getName();
exceptionCounts.merge(exceptionType, 1, Integer::sum);
}
System.out.println("Exceptions thrown:");
exceptionCounts.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEach(entry -> 
System.out.printf("  %s: %d times%n", entry.getKey(), entry.getValue()));
System.out.println();
}
private static void analyzeIOEvents(RecordingFile recording) throws IOException {
System.out.println("=== I/O Analysis ===");
List<RecordedEvent> fileReadEvents = recording.readEvents()
.filter(event -> event.getEventType().getName().equals("jdk.FileRead"))
.collect(Collectors.toList());
List<RecordedEvent> fileWriteEvents = recording.readEvents()
.filter(event -> event.getEventType().getName().equals("jdk.FileWrite"))
.collect(Collectors.toList());
long totalBytesRead = fileReadEvents.stream()
.mapToLong(event -> event.getLong("bytesRead"))
.sum();
long totalBytesWritten = fileWriteEvents.stream()
.mapToLong(event -> event.getLong("bytesWritten"))
.sum();
System.out.printf("Total bytes read: %.2f MB%n", totalBytesRead / (1024.0 * 1024.0));
System.out.printf("Total bytes written: %.2f MB%n", totalBytesWritten / (1024.0 * 1024.0));
System.out.printf("File read operations: %d%n", fileReadEvents.size());
System.out.printf("File write operations: %d%n", fileWriteEvents.size());
System.out.println();
}
static class AllocationStats {
long count = 0;
long totalSize = 0;
void addAllocation(long size) {
count++;
totalSize += size;
}
}
}

Advanced JFR Analysis

Custom Event Analysis and Correlation

import jdk.jfr.consumer.*;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class AdvancedJFRAnalysis {
public static class PerformanceAnalysis {
private final List<RecordedEvent> events;
public PerformanceAnalysis(List<RecordedEvent> events) {
this.events = events;
}
public void analyzePerformanceIssues() {
System.out.println("=== Performance Issue Analysis ===");
detectMemoryLeaks();
detectContentionIssues();
detectInefficientCode();
detectResourceExhaustion();
}
private void detectMemoryLeaks() {
System.out.println("\n--- Memory Leak Detection ---");
// Analyze GC and heap events for memory leak patterns
List<RecordedEvent> gcEvents = events.stream()
.filter(e -> e.getEventType().getName().contains("GarbageCollection"))
.collect(Collectors.toList());
List<RecordedEvent> heapSummaryEvents = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.GCHeapSummary"))
.collect(Collectors.toList());
if (heapSummaryEvents.size() > 2) {
RecordedEvent first = heapSummaryEvents.get(0);
RecordedEvent last = heapSummaryEvents.get(heapSummaryEvents.size() - 1);
long initialHeapUsed = first.getLong("heapUsed");
long finalHeapUsed = last.getLong("heapUsed");
if (finalHeapUsed > initialHeapUsed * 1.5) {
System.out.println("⚠️  Potential memory leak detected:");
System.out.printf("   Heap used increased from %.2f MB to %.2f MB%n",
initialHeapUsed / (1024.0 * 1024.0),
finalHeapUsed / (1024.0 * 1024.0));
}
}
}
private void detectContentionIssues() {
System.out.println("\n--- Lock Contention Analysis ---");
List<RecordedEvent> monitorEnterEvents = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.JavaMonitorEnter"))
.collect(Collectors.toList());
// Find monitors with high contention
Map<String, ContentionStats> monitorStats = new HashMap<>();
for (RecordedEvent event : monitorEnterEvents) {
String monitorClass = event.getClass("monitorClass").getName();
Duration duration = event.getDuration();
if (duration != null) {
long waitTime = duration.toNanos();
monitorStats
.computeIfAbsent(monitorClass, k -> new ContentionStats())
.addWaitTime(waitTime);
}
}
monitorStats.entrySet().stream()
.filter(entry -> entry.getValue().totalWaitTime > TimeUnit.MILLISECONDS.toNanos(100))
.sorted((e1, e2) -> Long.compare(e2.getValue().totalWaitTime, e1.getValue().totalWaitTime))
.limit(5)
.forEach(entry -> {
ContentionStats stats = entry.getValue();
System.out.printf("⚠️  High contention on: %s%n", entry.getKey());
System.out.printf("   Total wait time: %.3f ms across %d operations%n",
stats.totalWaitTime / 1_000_000.0, stats.operationCount);
});
}
private void detectInefficientCode() {
System.out.println("\n--- Inefficient Code Detection ---");
// Analyze method execution times
List<RecordedEvent> executionSamples = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.ExecutionSample"))
.collect(Collectors.toList());
// Find methods that appear frequently in slow execution paths
Map<String, Long> methodExecutionTime = new HashMap<>();
for (RecordedEvent event : executionSamples) {
RecordedStackTrace stackTrace = event.getStackTrace();
if (stackTrace != null) {
// Give weight to methods deeper in the call stack (potentially called many times)
List<RecordedFrame> frames = stackTrace.getFrames();
for (int i = 0; i < Math.min(frames.size(), 5); i++) {
RecordedFrame frame = frames.get(i);
String methodName = frame.getMethod().getType().getName() + 
"." + frame.getMethod().getName();
methodExecutionTime.merge(methodName, 1L << i, Long::sum);
}
}
}
System.out.println("Potential optimization candidates:");
methodExecutionTime.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(8)
.forEach(entry -> 
System.out.printf("  %s (score: %d)%n", entry.getKey(), entry.getValue()));
}
private void detectResourceExhaustion() {
System.out.println("\n--- Resource Exhaustion Detection ---");
// Check for high CPU usage periods
List<RecordedEvent> cpuLoadEvents = events.stream()
.filter(e -> e.getEventType().getName().equals("jdk.CPULoad"))
.collect(Collectors.toList());
for (RecordedEvent event : cpuLoadEvents) {
double machineLoad = event.getDouble("machineTotal");
if (machineLoad > 0.9) {
System.out.printf("⚠️  High CPU load detected: %.1f%%%n", machineLoad * 100);
break;
}
}
// Check for memory pressure
List<RecordedEvent> gcPauseEvents = events.stream()
.filter(e -> e.getEventType().getName().contains("GarbageCollection"))
.collect(Collectors.toList());
long totalGCPauseTime = gcPauseEvents.stream()
.mapToLong(e -> e.getLong("duration"))
.sum();
if (totalGCPauseTime > TimeUnit.SECONDS.toNanos(10)) {
System.out.printf("⚠️  High GC pressure: %.1f seconds spent in GC%n",
totalGCPauseTime / 1_000_000_000.0);
}
}
}
static class ContentionStats {
long operationCount = 0;
long totalWaitTime = 0;
void addWaitTime(long waitTime) {
operationCount++;
totalWaitTime += waitTime;
}
}
}

Call Tree Analysis for Performance Profiling

import jdk.jfr.consumer.*;
import java.util.*;
import java.util.stream.Collectors;
public class CallTree {
private final Node root = new Node("root");
public void addStackTrace(RecordedStackTrace stackTrace) {
List<RecordedFrame> frames = stackTrace.getFrames();
if (frames == null || frames.isEmpty()) return;
Node current = root;
// Traverse from top of stack (most recent call) to bottom
for (int i = frames.size() - 1; i >= 0; i--) {
RecordedFrame frame = frames.get(i);
String methodName = getMethodName(frame);
current = current.getOrAddChild(methodName);
}
root.incrementCount();
}
private String getMethodName(RecordedFrame frame) {
RecordedMethod method = frame.getMethod();
if (method == null) return "unknown";
return method.getType().getName() + "." + method.getName() + "()";
}
public void printTopMethods(int limit) {
List<Node> hotMethods = root.children.values().stream()
.sorted((a, b) -> Long.compare(b.count, a.count))
.limit(limit)
.collect(Collectors.toList());
for (Node method : hotMethods) {
System.out.printf("  %s: %d samples%n", method.name, method.count);
}
}
public void printCallTree(int maxDepth) {
printNode(root, 0, maxDepth);
}
private void printNode(Node node, int depth, int maxDepth) {
if (depth > maxDepth) return;
String indent = "  ".repeat(depth);
System.out.printf("%s%s: %d%n", indent, node.name, node.count);
node.children.values().stream()
.sorted((a, b) -> Long.compare(b.count, a.count))
.limit(5) // Show top 5 children
.forEach(child -> printNode(child, depth + 1, maxDepth));
}
static class Node {
final String name;
long count = 0;
final Map<String, Node> children = new HashMap<>();
Node(String name) {
this.name = name;
}
Node getOrAddChild(String methodName) {
return children.computeIfAbsent(methodName, Node::new);
}
void incrementCount() {
count++;
}
}
}

Real-time JFR Monitoring

Continuous JFR Monitoring System

import jdk.jfr.*;
import jdk.jfr.consumer.*;
import java.io.IOException;
import java.nio.file.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class RealTimeJFRMonitor {
private final AtomicBoolean running = new AtomicBoolean(false);
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
private final List<JFRListener> listeners = new CopyOnWriteArrayList<>();
private Recording continuousRecording;
public void startMonitoring() throws Exception {
if (running.get()) {
throw new IllegalStateException("Monitor is already running");
}
running.set(true);
// Start continuous recording
continuousRecording = new Recording();
continuousRecording.setName("Continuous-Monitoring");
continuousRecording.setToDisk(true);
continuousRecording.setMaxAge(Duration.ofMinutes(10));
continuousRecording.setMaxSize(100 * 1024 * 1024); // 100MB
// Enable specific events for monitoring
continuousRecording.enable("jdk.GarbageCollection").withPeriod(Duration.ofSeconds(1));
continuousRecording.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
continuousRecording.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10));
continuousRecording.enable("jdk.ExceptionThrown");
continuousRecording.enable("jdk.FileRead").withThreshold(Duration.ofMillis(50));
continuousRecording.enable("jdk.FileWrite").withThreshold(Duration.ofMillis(50));
continuousRecording.start();
// Start analysis thread
scheduler.scheduleAtFixedRate(this::analyzeRecentEvents, 5, 5, TimeUnit.SECONDS);
System.out.println("JFR Real-time Monitoring Started");
}
public void stopMonitoring() throws Exception {
if (!running.get()) return;
running.set(false);
scheduler.shutdown();
if (continuousRecording != null) {
continuousRecording.stop();
continuousRecording.close();
}
System.out.println("JFR Real-time Monitoring Stopped");
}
private void analyzeRecentEvents() {
try {
Path recordingFile = Files.createTempFile("jfr-analysis", ".jfr");
continuousRecording.dump(recordingFile);
try (RecordingFile recording = new RecordingFile(recordingFile)) {
while (recording.hasMoreEvents()) {
RecordedEvent event = recording.readEvent();
notifyListeners(event);
// Perform real-time analysis
analyzeEvent(event);
}
}
Files.deleteIfExists(recordingFile);
} catch (IOException e) {
System.err.println("Error analyzing events: " + e.getMessage());
}
}
private void analyzeEvent(RecordedEvent event) {
String eventType = event.getEventType().getName();
switch (eventType) {
case "jdk.GarbageCollection":
analyzeRealTimeGC(event);
break;
case "jdk.JavaMonitorEnter":
analyzeRealTimeLockContention(event);
break;
case "jdk.ExceptionThrown":
analyzeRealTimeException(event);
break;
case "jdk.CPULoad":
analyzeRealTimeCPULoad(event);
break;
}
}
private void analyzeRealTimeGC(RecordedEvent event) {
long duration = event.getLong("duration");
if (duration > TimeUnit.MILLISECONDS.toNanos(100)) {
System.out.printf("⚠️  Long GC pause: %.2f ms%n", duration / 1_000_000.0);
}
}
private void analyzeRealTimeLockContention(RecordedEvent event) {
Duration duration = event.getDuration();
if (duration != null && duration.toMillis() > 100) {
String monitorClass = event.getClass("monitorClass").getName();
System.out.printf("⚠️  Long lock wait: %s for %.2f ms%n", 
monitorClass, duration.toNanos() / 1_000_000.0);
}
}
private void analyzeRealTimeException(RecordedEvent event) {
String exceptionType = event.getClass("thrownClass").getName();
System.out.printf("⚠️  Exception thrown: %s%n", exceptionType);
}
private void analyzeRealTimeCPULoad(RecordedEvent event) {
double jvmUser = event.getDouble("jvmUser");
if (jvmUser > 0.8) {
System.out.printf("⚠️  High JVM CPU usage: %.1f%%%n", jvmUser * 100);
}
}
public void addListener(JFRListener listener) {
listeners.add(listener);
}
public void removeListener(JFRListener listener) {
listeners.remove(listener);
}
private void notifyListeners(RecordedEvent event) {
for (JFRListener listener : listeners) {
try {
listener.onEvent(event);
} catch (Exception e) {
System.err.println("Error in event listener: " + e.getMessage());
}
}
}
public interface JFRListener {
void onEvent(RecordedEvent event);
}
// Example listener implementation
public static class AlertingListener implements JFRListener {
private final Map<String, Integer> exceptionCounts = new HashMap<>();
private final Map<String, Integer> longGCCounts = new HashMap<>();
@Override
public void onEvent(RecordedEvent event) {
String eventType = event.getEventType().getName();
if ("jdk.ExceptionThrown".equals(eventType)) {
handleExceptionEvent(event);
} else if ("jdk.GarbageCollection".equals(eventType)) {
handleGCEvent(event);
}
}
private void handleExceptionEvent(RecordedEvent event) {
String exceptionType = event.getClass("thrownClass").getName();
int count = exceptionCounts.merge(exceptionType, 1, Integer::sum);
if (count >= 10) {
System.out.printf("🚨 ALERT: High exception rate for %s: %d exceptions%n", 
exceptionType, count);
}
}
private void handleGCEvent(RecordedEvent event) {
long duration = event.getLong("duration");
if (duration > TimeUnit.MILLISECONDS.toNanos(500)) {
String gcName = event.getString("name");
int count = longGCCounts.merge(gcName, 1, Integer::sum);
if (count >= 3) {
System.out.printf("🚨 ALERT: Multiple long GC pauses for %s: %d occurrences%n", 
gcName, count);
}
}
}
}
}

JFR Integration with Application Metrics

Custom JFR Events and Monitoring

import jdk.jfr.*;
import java.util.concurrent.atomic.*;
public class CustomJFREvents {
// Custom JFR event for business transactions
@Label("Business Transaction")
@Description("Represents a business transaction execution")
@Category("Business")
public static class BusinessTransactionEvent extends Event {
@Label("Transaction Type")
private String transactionType;
@Label("Transaction ID")
private String transactionId;
@Label("Duration")
private long durationMs;
@Label("Success")
private boolean success;
@Label("Error Message")
private String errorMessage;
// Getters and setters
public String getTransactionType() { return transactionType; }
public void setTransactionType(String transactionType) { this.transactionType = transactionType; }
public String getTransactionId() { return transactionId; }
public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
public long getDurationMs() { return durationMs; }
public void setDurationMs(long durationMs) { this.durationMs = durationMs; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
}
// Custom JFR event for cache operations
@Label("Cache Operation")
@Description("Represents a cache operation")
@Category("Performance")
public static class CacheOperationEvent extends Event {
@Label("Operation Type")
private String operationType;
@Label("Cache Name")
private String cacheName;
@Label("Key")
private String key;
@Label("Duration Nanos")
private long durationNanos;
@Label("Hit")
private boolean hit;
// Getters and setters
public String getOperationType() { return operationType; }
public void setOperationType(String operationType) { this.operationType = operationType; }
public String getCacheName() { return cacheName; }
public void setCacheName(String cacheName) { this.cacheName = cacheName; }
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public long getDurationNanos() { return durationNanos; }
public void setDurationNanos(long durationNanos) { this.durationNanos = durationNanos; }
public boolean isHit() { return hit; }
public void setHit(boolean hit) { this.hit = hit; }
}
// Business service that uses custom JFR events
public static class BusinessService {
private final AtomicLong transactionIdGenerator = new AtomicLong();
public void processTransaction(String transactionType, Object data) {
BusinessTransactionEvent event = new BusinessTransactionEvent();
event.begin();
String transactionId = "TXN-" + transactionIdGenerator.incrementAndGet();
event.setTransactionType(transactionType);
event.setTransactionId(transactionId);
boolean success = false;
String errorMessage = null;
long startTime = System.currentTimeMillis();
try {
// Simulate business processing
Thread.sleep(50 + (long)(Math.random() * 100));
// Simulate occasional failures
if (Math.random() < 0.05) {
throw new RuntimeException("Simulated business error");
}
success = true;
} catch (Exception e) {
errorMessage = e.getMessage();
success = false;
} finally {
long duration = System.currentTimeMillis() - startTime;
event.setDurationMs(duration);
event.setSuccess(success);
event.setErrorMessage(errorMessage);
event.commit();
}
}
}
// Cache service with JFR events
public static class CacheService {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public Object get(String cacheName, String key) {
CacheOperationEvent event = new CacheOperationEvent();
event.begin();
event.setOperationType("GET");
event.setCacheName(cacheName);
event.setKey(key);
long startTime = System.nanoTime();
boolean hit = cache.containsKey(key);
Object value = cache.get(key);
long duration = System.nanoTime() - startTime;
event.setDurationNanos(duration);
event.setHit(hit);
event.commit();
return value;
}
public void put(String cacheName, String key, Object value) {
CacheOperationEvent event = new CacheOperationEvent();
event.begin();
event.setOperationType("PUT");
event.setCacheName(cacheName);
event.setKey(key);
long startTime = System.nanoTime();
cache.put(key, value);
long duration = System.nanoTime() - startTime;
event.setDurationNanos(duration);
event.setHit(false); // PUT operations are always considered misses for hit rate
event.commit();
}
}
}

JFR Analysis Report Generator

import jdk.jfr.consumer.*;
import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.*;
public class JFRReportGenerator {
public static void generateComprehensiveReport(String jfrFilePath, String outputPath) throws IOException {
Path jfrFile = Paths.get(jfrFilePath);
Path htmlReport = Paths.get(outputPath, "jfr-analysis-report.html");
try (RecordingFile recording = new RecordingFile(jfrFile)) {
String htmlContent = generateHTMLReport(recording);
Files.write(htmlReport, htmlContent.getBytes());
System.out.println("JFR Analysis Report generated: " + htmlReport.toAbsolutePath());
}
}
private static String generateHTMLReport(RecordingFile recording) throws IOException {
StringBuilder html = new StringBuilder();
html.append("""
<!DOCTYPE html>
<html>
<head>
<title>JFR Analysis Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; }
.section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
table { border-collapse: collapse; width: 100%; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.warning { background-color: #fff3cd; border-color: #ffeaa7; }
.critical { background-color: #f8d7da; border-color: #f5c6cb; }
.metric-card { 
display: inline-block; 
width: 200px; 
margin: 10px; 
padding: 15px; 
border: 1px solid #ddd; 
border-radius: 5px; 
text-align: center;
}
.chart { margin: 20px 0; }
</style>
</head>
<body>
<div class="header">
<h1>Java Flight Recorder Analysis Report</h1>
<p>Generated on: """ + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + """</p>
</div>
""");
// Collect all events for analysis
List<RecordedEvent> events = recording.readEvents().collect(Collectors.toList());
// Generate report sections
generateSummarySection(html, events);
generateGCSection(html, events);
generateCPUSection(html, events);
generateMemorySection(html, events);
generateExceptionSection(html, events);
generateIOSection(html, events);
generateRecommendationsSection(html, events);
html.append("</body></html>");
return html.toString();
}
private static void generateSummarySection(StringBuilder html, List<RecordedEvent> events) {
html.append("""
<div class="section">
<h2>Executive Summary</h2>
<div class="metric-card">
<h3>Total Events</h3>
<p style="font-size: 24px; font-weight: bold;">""" + events.size() + """</p>
</div>
<div class="metric-card">
<h3>Recording Duration</h3>
<p style="font-size: 24px; font-weight: bold;">""" + 
getRecordingDuration(events) + """</p>
</div>
<div class="metric-card">
<h3>Events Types</h3>
<p style="font-size: 24px; font-weight: bold;">""" + 
countEventTypes(events) + """</p>
</div>
</div>
""");
}
private static String getRecordingDuration(List<RecordedEvent> events) {
if (events.isEmpty()) return "N/A";
Instant start = events.get(0).getStartTime();
Instant end = events.get(events.size() - 1).getStartTime();
Duration duration = Duration.between(start, end);
return String.format("%d min %d sec", 
duration.toMinutes(), duration.toSecondsPart());
}
private static long countEventTypes(List<RecordedEvent> events) {
return events.stream()
.map(e -> e.getEventType().getName())
.distinct()
.count();
}
// Other section generation methods would be implemented similarly...
private static void generateGCSection(StringBuilder html, List<RecordedEvent> events) {
List<RecordedEvent> gcEvents = events.stream()
.filter(e -> e.getEventType().getName().contains("GarbageCollection"))
.collect(Collectors.toList());
html.append("""
<div class="section">
<h2>Garbage Collection Analysis</h2>
<table>
<tr><th>Metric</th><th>Value</th></tr>
""");
if (!gcEvents.isEmpty()) {
long totalGCTime = gcEvents.stream()
.mapToLong(e -> e.getLong("duration"))
.sum();
long maxGCTime = gcEvents.stream()
.mapToLong(e -> e.getLong("duration"))
.max()
.orElse(0);
html.append("<tr><td>Total GC Time</td><td>")
.append(String.format("%.2f sec", totalGCTime / 1_000_000_000.0))
.append("</td></tr>");
html.append("<tr><td>Longest GC Pause</td><td>")
.append(String.format("%.2f ms", maxGCTime / 1_000_000.0))
.append("</td></tr>");
html.append("<tr><td>GC Events</td><td>")
.append(gcEvents.size())
.append("</td></tr>");
} else {
html.append("<tr><td colspan='2'>No GC events recorded</td></tr>");
}
html.append("</table></div>");
}
// Implement other sections similarly...
private static void generateCPUSection(StringBuilder html, List<RecordedEvent> events) {
// CPU analysis implementation
}
private static void generateMemorySection(StringBuilder html, List<RecordedEvent> events) {
// Memory analysis implementation
}
private static void generateExceptionSection(StringBuilder html, List<RecordedEvent> events) {
// Exception analysis implementation
}
private static void generateIOSection(StringBuilder html, List<RecordedEvent> events) {
// I/O analysis implementation
}
private static void generateRecommendationsSection(StringBuilder html, List<RecordedEvent> events) {
// Recommendations based on analysis
html.append("""
<div class="section">
<h2>Performance Recommendations</h2>
<ul>
""");
// Add recommendations based on analysis
html.append("<li>Monitor GC behavior and consider tuning heap sizes</li>");
html.append("<li>Review methods with high CPU usage for optimization</li>");
html.append("<li>Check for memory leaks in long-running operations</li>");
html.append("<li>Monitor lock contention and consider reducing synchronization</li>");
html.append("</ul></div>");
}
}

Usage Examples

Basic JFR Analysis

public class JFRExample {
public static void main(String[] args) throws Exception {
// Analyze existing JFR file
JFRAnalyzer analyzer = new JFRAnalyzer();
analyzer.analyzeRecording("my-recording.jfr");
// Start real-time monitoring
RealTimeJFRMonitor monitor = new RealTimeJFRMonitor();
monitor.addListener(new RealTimeJFRMonitor.AlertingListener());
monitor.startMonitoring();
// Run application
runApplication();
// Stop monitoring
monitor.stopMonitoring();
}
private static void runApplication() {
CustomJFREvents.BusinessService businessService = new CustomJFREvents.BusinessService();
CustomJFREvents.CacheService cacheService = new CustomJFREvents.CacheService();
// Simulate application workload
for (int i = 0; i < 1000; i++) {
businessService.processTransaction("ORDER_PROCESSING", "order-data");
cacheService.put("orders", "order-" + i, "order-data-" + i);
}
}
}

Key Features Summary

  1. Comprehensive JFR Analysis: GC, CPU, memory, exceptions, I/O
  2. Real-time Monitoring: Continuous JFR event processing
  3. Custom Events: Application-specific monitoring
  4. Performance Detection: Automatic issue detection
  5. HTML Reporting: Professional analysis reports
  6. Call Tree Analysis: Method-level performance profiling

This JMC Flight Recorder analysis framework provides deep insights into Java application performance and helps identify optimization opportunities.

Leave a Reply

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


Macro Nepal Helper