Discovering JFR: A Journey into Modern Java Profiling Excellence
Symptom: memory usage drifts under real production load. Constraint: profiling cannot degrade latency or throughput. Solution: Java Flight Recorder (JFR) — the built‑in, low‑overhead recorder that captures CPU, memory, GC, I/O, and custom events from live systems with typically under 2% impact.
With JFR, production evidence replaces guesswork. You can analyze true workloads, correlate behavior with business metrics, and turn performance tuning into a routine, low‑risk activity.
Why JFR Matters in the Modern Java Landscape
The Evolution of Java Profiling
Traditional Java profiling has long been dominated by tools that introduce significant overhead, making them unsuitable for production environments. The typical workflow looked like this:
Traditional Approach:
1. Profile in development environments with available tools
2. Use established profilers with known characteristics
3. Work with development environment data and patterns
4. Analyze available data to understand application behavior
The JFR Advantage:
1. Enable continuous profiling in production with <2% overhead
2. Collect comprehensive runtime data while maintaining excellent user experience
3. Analyze real production workloads and discover interesting edge cases
4. Correlate performance data with actual business metrics for deeper insights
What Makes JFR Special
Java Flight Recorder isn't just another profiling tool—it's a built-in, always-available data collection framework that's been part of the JDK since Java 11 (and available commercially since Java 7). Here's what sets it apart:
- Ultra-Low Overhead: Typically less than 2% performance impact in production
- Comprehensive Data: Captures CPU usage, memory allocation, GC behavior, I/O operations, and custom application events
- Production-Ready: Designed from the ground up for continuous production monitoring
- Time-Series Data: Provides temporal context that static profiling tools miss
- Integration-Friendly: Works seamlessly with modern observability stacks
The Java 25 JFR Renaissance
Revolutionary Enhancements in Modern Java
With Java 21, 24, and 25, JFR has undergone significant enhancements that transform it from a diagnostic tool into a comprehensive application intelligence platform:
JEP 518: JFR Cooperative Sampling
Java 25 introduces cooperative sampling, a breakthrough that reduces profiling overhead even further while improving measurement accuracy:
# Traditional CPU profiling (higher overhead)
jcmd <pid> JFR.start duration=60s events=jdk.ExecutionSample
# Java 25 cooperative sampling (minimal overhead)
jcmd <pid> JFR.start duration=60s \
"jdk.CPUTimeSample#enabled=true" \
"jdk.ExecutionSample#enabled=true" \
"jdk.NativeMethodSample#enabled=true"
JEP 520: Enhanced Method Timing & Tracing
The new method timing capabilities provide unprecedented visibility into application behavior:
# Enable detailed method tracing for specific packages
jcmd <pid> JFR.start duration=30s \
"jdk.MethodSample#enabled=true" \
"jdk.MethodEntry#enabled=true,threshold=1ms" \
"jdk.MethodExit#enabled=true,threshold=1ms" \
filename=method-trace.jfr
Native Memory Profiling Evolution
Java 25 enhances native memory profiling with better integration and reduced overhead:
# Enhanced native memory tracking with JFR integration
jcmd <pid> JFR.start duration=120s \
"jdk.ObjectAllocationInNewTLAB#enabled=true,stackTrace=true" \
"jdk.ObjectAllocationOutsideTLAB#enabled=true,stackTrace=true" \
"jdk.NativeMemoryUsage#enabled=true" \
filename=native-memory-analysis.jfr
Practical JFR Mastery: From Basics to Production
The Modern JFR Workflow
Let's walk through a comprehensive approach to JFR profiling that leverages both traditional techniques and Java 25 enhancements:
1. Problem-Driven Profiling Strategy
Before starting any profiling session, identify your specific performance concern:
# Memory leak detection (long-running analysis)
jcmd <pid> JFR.start name=memory-leak-analysis duration=600s \
disk=true maxsize="1GB" maxage="30m" \
"jdk.ObjectAllocationInNewTLAB#enabled=true,stackTrace=true" \
"jdk.ObjectAllocationOutsideTLAB#enabled=true,stackTrace=true" \
"jdk.TLABAllocation#enabled=true" \
"jdk.OldObjectSample#enabled=true,cutoff=0ms" \
filename=memory-leak-analysis.jfr
# CPU hotspot identification (focused analysis)
jcmd <pid> JFR.start name=cpu-analysis duration=60s \
"jdk.ExecutionSample#enabled=true" \
"jdk.CPUTimeSample#enabled=true" \
"jdk.ThreadCPULoad#enabled=true" \
filename=cpu-hotspots.jfr
# I/O bottleneck detection (comprehensive I/O analysis)
jcmd <pid> JFR.start name=io-analysis duration=120s \
"jdk.SocketRead#enabled=true,threshold=10ms" \
"jdk.SocketWrite#enabled=true,threshold=10ms" \
"jdk.FileRead#enabled=true,threshold=10ms" \
"jdk.FileWrite#enabled=true,threshold=10ms" \
filename=io-bottlenecks.jfr
2. Advanced Event Configuration
Modern JFR allows fine-grained control over what data to collect:
# Custom event configuration for microservices
jcmd <pid> JFR.start name=microservice-analysis duration=300s \
"jdk.ObjectAllocationSample#enabled=true,throttle=1000/s" \
"jdk.JavaMonitorEnter#enabled=true,threshold=10ms" \
"jdk.JavaMonitorWait#enabled=true,threshold=10ms" \
"jdk.ThreadPark#enabled=true,threshold=10ms" \
"jdk.GCHeapSummary#enabled=true,period=10s" \
"jdk.ThreadContextSwitchRate#enabled=true" \
filename=microservice-comprehensive.jfr
3. Integration with Modern Tooling
JFR recordings integrate seamlessly with analysis tools:
# Convert JFR to flame graphs using async-profiler
java -jar converter.jar jfr2flame recording.jfr flamegraph.html
# Export to OpenTelemetry format (Java 25)
asprof -d 60 -o otlp -f telemetry-data.json <pid>
# Generate interactive heatmaps
jfrconv --cpu -o heatmap recording.jfr heatmap.html
Real-World Case Study: Optimizing Memory Usage with JFR
Let's explore how modern JFR techniques help optimize application performance:
The Opportunity
A Spring Boot microservice showed interesting memory usage patterns with gradual growth over time. This presented a perfect opportunity to apply JFR's comprehensive memory analysis capabilities.
The JFR Analysis Approach
Step 1: Long-term Memory Allocation Tracking
# Start comprehensive memory leak detection
jcmd 12345 JFR.start name=memory-leak-investigation duration=1800s \
disk=true maxsize="2GB" maxage="60m" \
"jdk.ObjectAllocationInNewTLAB#enabled=true,stackTrace=true" \
"jdk.ObjectAllocationOutsideTLAB#enabled=true,stackTrace=true" \
"jdk.ObjectAllocationSample#enabled=true,stackTrace=true" \
"jdk.TLABAllocation#enabled=true" \
"jdk.TLABWaste#enabled=true" \
"jdk.OldObjectSample#enabled=true,cutoff=0ms" \
"jdk.GCHeapSummary#enabled=true,period=30s" \
"jdk.ClassLoaderStatistics#enabled=true,period=60s" \
filename=memory-leak-30min.jfr
Step 2: Analysis Results
The JFR recording revealed:
- 85% of allocations were HashMap
objects in a specific service method
- Objects were being retained in a static cache without proper cleanup
- TLAB allocation patterns showed consistent growth without corresponding deallocation
Step 3: The Fix
// Before: Unbounded cache causing memory leak
private static final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
// After: Bounded cache with proper cleanup
private static final Map<String, CacheEntry> cache =
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofHours(1))
.build()
.asMap();
Best Practices for Production JFR
1. Continuous Monitoring Strategy
Implement JFR as part of your observability stack:
# Production-safe continuous profiling
jcmd <pid> JFR.start name=continuous-monitoring \
disk=true maxsize="100MB" maxage="1h" \
"jdk.CPULoad#enabled=true,period=10s" \
"jdk.GCHeapSummary#enabled=true,period=30s" \
"jdk.ObjectAllocationSample#enabled=true,throttle=100/s" \
filename=continuous-monitoring.jfr
2. Event Filtering and Throttling
Optimize data collection for production environments:
# Throttled allocation tracking (production-safe)
"jdk.ObjectAllocationSample#enabled=true,throttle=1000/s,stackTrace=false"
# Threshold-based I/O monitoring (focus on slow operations)
"jdk.FileRead#enabled=true,threshold=50ms"
"jdk.SocketRead#enabled=true,threshold=100ms"
3. Automated Analysis Pipeline
Create automated JFR analysis workflows:
#!/bin/bash
# Automated JFR analysis script
JFR_FILE="production-$(date +%Y%m%d-%H%M%S).jfr"
# Collect JFR data
jcmd $(pgrep java) JFR.start duration=300s filename="$JFR_FILE"
# Wait for completion
sleep 305
# Convert to analysis formats
java -jar converter.jar jfr2flame "$JFR_FILE" "analysis-$(date +%Y%m%d-%H%M%S).html"
# Upload to monitoring system
curl -X POST -F "file=@$JFR_FILE" https://monitoring.company.com/jfr-upload
Advanced JFR Techniques
Custom Event Creation
Extend JFR with application-specific events:
@Name("com.example.BusinessTransaction")
@Label("Business Transaction")
@Category("Application")
public class BusinessTransactionEvent extends Event {
@Label("Transaction Type")
String transactionType;
@Label("Customer ID")
String customerId;
@Label("Processing Time")
@Timespan(Timespan.MILLISECONDS)
long processingTime;
}
// Usage in application code
public void processTransaction(String type, String customerId) {
BusinessTransactionEvent event = new BusinessTransactionEvent();
event.transactionType = type;
event.customerId = customerId;
event.begin();
try {
// Business logic here
performTransaction(type, customerId);
} finally {
event.end();
if (event.shouldCommit()) {
event.commit();
}
}
}
JFR with Containerized Applications
Optimize JFR for Kubernetes environments:
# Kubernetes deployment with JFR
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-jfr
spec:
template:
spec:
containers:
- name: java-app
image: openjdk:21-jre
env:
- name: JAVA_OPTS
value: >-
-XX:+FlightRecorder
-XX:StartFlightRecording=duration=0,filename=/tmp/continuous.jfr,maxsize=100m,maxage=1h
-XX:FlightRecorderOptions=disk=true
volumeMounts:
- name: jfr-data
mountPath: /tmp
volumes:
- name: jfr-data
emptyDir: {}
The Future of Java Performance Analysis
Emerging Trends
JFR continues to evolve with the Java platform:
- Enhanced Integration: Deeper integration with cloud-native monitoring platforms
- Machine Learning: AI-powered anomaly detection in JFR data streams
- Real-time Analysis: Streaming JFR data analysis for immediate insights
- Cross-Language Profiling: JFR integration with GraalVM native images
Getting Started Today
To begin mastering JFR in your organization:
- Start Small: Begin with development environment profiling
- Educate Teams: Train developers on JFR basics and interpretation
- Integrate Gradually: Add JFR to staging environments first
- Automate Analysis: Build automated JFR analysis into CI/CD pipelines
- Monitor Continuously: Implement continuous JFR profiling in production
Conclusion: JFR as Your Performance Intelligence Platform
Java Flight Recorder opens up exciting possibilities for understanding and optimizing application performance. It's not just a profiling tool—it's a comprehensive performance intelligence platform that enables:
- Proactive Performance Insights: Discover optimization opportunities before they become bottlenecks
- Data-Driven Enhancement: Make informed performance improvements based on real production data
- Comprehensive Understanding: Gain deep insights into application behavior with detailed runtime data
- Intelligent Resource Usage: Optimize resource allocation based on actual application patterns
The enhancements in Java 21, 24, and 25 make JFR even more powerful, with cooperative sampling reducing overhead and enhanced method tracing providing unprecedented visibility into application behavior.
Your Learning Journey:
1. Explore JFR in your development environment using the examples in this article
2. Discover interesting performance patterns in your applications using JFR-based analysis
3. Implement continuous JFR monitoring to build comprehensive performance insights
4. Share JFR knowledge with your team to elevate everyone's performance engineering skills
The future of Java performance optimization is here, and JFR provides the foundation for building exceptional, high-performance applications. Every profiling session is an opportunity to learn something new about your application and improve its performance.
Ready to dive deeper into JFR? Check out the java-cursor-rules project for automated JFR profiling workflows and interactive profiling scripts that implement the techniques described in this article.