Introduction to Java Mission Control
Java Mission Control is an advanced performance monitoring and profiling tool suite for Java applications. It's included with the Oracle JDK and provides deep insights into JVM behavior with minimal performance overhead.
Key Components
- JMC (Java Mission Control): GUI tool for monitoring and analysis
- JFR (Java Flight Recorder): Low-overhead profiling engine
- JMX (Java Management Extensions): Management and monitoring infrastructure
1. Java Flight Recorder (JFR) Fundamentals
What is JFR?
- Low-overhead event-based profiling system
- Built into the JVM (Oracle JDK + OpenJDK 11+)
- Continuous monitoring with minimal performance impact (typically 1-2%)
- Production-safe profiling
JFR Event Types
// Common JFR event categories: // - JVM: GC, class loading, JIT compilation // - System: CPU, memory, file I/O, network // - Application: Custom events, method profiling // - Threads: Lock contention, thread states
2. Enabling and Configuring JFR
Command-Line Activation
Production Mode (Low Overhead)
# Basic JFR enabling java -XX:+FlightRecorder -XX:StartFlightRecording=delay=30s,duration=60s,name=MyRecording,filename=myrecording.jfr -jar myapp.jar # Continuous recording with disk rotation java -XX:+FlightRecorder -XX:StartFlightRecording=disk=true,maxage=1h,maxsize=1g,name=ContinuousRecording -jar myapp.jar
Diagnostic Mode (Detailed Profiling)
# Higher detail for troubleshooting java -XX:+FlightRecorder -XX:StartFlightRecording=settings=profile,duration=10m,name=DiagnosticRecording -jar myapp.jar
JFR Startup Configurations
# Different configuration profiles java -XX:+FlightRecorder \ -XX:FlightRecorderOptions=defaultrecording=true,disk=true,maxchunksize=100M \ -jar myapp.jar # Custom settings file java -XX:+FlightRecorder \ -XX:FlightRecorderOptions=settings=mycustom.jfc \ -jar myapp.jar
Runtime Control via JCMD
# Start recording on running JVM jcmd <pid> JFR.start name=RuntimeRecording duration=5m filename=output.jfr # Check recording status jcmd <pid> JFR.check # Stop specific recording jcmd <pid> JFR.stop name=RuntimeRecording # Dump recording data jcmd <pid> JFR.dump name=RuntimeRecording filename=dump.jfr
3. Practical Profiling Examples
Memory Leak Detection
public class MemoryLeakExample {
private static final List<byte[]> LEAKING_LIST = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
// Simulate memory leak
for (int i = 0; i < 1000; i++) {
LEAKING_LIST.add(new byte[1024 * 1024]); // 1MB chunks
Thread.sleep(100);
if (i % 100 == 0) {
System.out.println("Allocated " + i + " MB");
}
}
}
}
JFR Analysis Steps:
- Record with
settings=profile - Analyze GC and Allocation events
- Look for Old Generation growth
- Identify allocating methods in Method Profiling
Performance Bottleneck Identification
public class PerformanceExample {
private static final Map<String, String> CACHE = new HashMap<>();
public String processRequest(String input) {
// Simulate CPU-intensive operation
String result = expensiveCalculation(input);
// Simulate I/O operation
saveToDatabase(result);
return result;
}
private String expensiveCalculation(String input) {
// Method that shows up in CPU profiling
long start = System.nanoTime();
try {
Thread.sleep(10); // Simulate work
return input.toUpperCase() + Math.random();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return input;
} finally {
long duration = System.nanoTime() - start;
if (duration > 15_000_000) { // 15ms
System.err.println("Slow calculation: " + duration + " ns");
}
}
}
private void saveToDatabase(String data) {
try {
Thread.sleep(5); // Simulate database I/O
CACHE.put(data, "processed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
4. JMC GUI Deep Dive
Key Views and Tabs
General Tab
- Recording Overview: Duration, size, event statistics
- JVM Information: Version, arguments, system properties
- Recording Settings: Active event types and thresholds
Memory Tab
// Key metrics to monitor: // - Heap Usage (Young/Old Generation) // - GC Pauses and Frequency // - Allocation Pressure // - Object Allocation by Type // - GC Configuration Efficiency
Code Tab
- Hot Methods: CPU consumption by method
- Exception Profiling: Most frequent exceptions
- Compilation: JIT compiler activity
- Class Loading: Time spent loading classes
Threads Tab
- Thread States: Running, blocked, waiting
- Lock Instances: Contended locks
- Thread Dumps: Timed thread snapshots
- I/O Wait: File and socket operations
I/O Tab
- File Read/Write: File operation latency
- Socket Read/Write: Network I/O performance
- I/O by Thread: Which threads are I/O bound
Custom Event Analysis
// Creating custom JFR events
import jdk.jfr.Event;
import jdk.jfr.Category;
import jdk.jfr.Label;
import jdk.jfr.Description;
@Category("Business")
@Label("Order Processing Event")
@Description("Tracks order processing performance")
public class OrderProcessingEvent extends Event {
@Label("Order ID")
private String orderId;
@Label("Processing Time")
private long processingTime;
@Label("Success")
private boolean success;
// Constructors, getters, setters
public OrderProcessingEvent(String orderId, long processingTime, boolean success) {
this.orderId = orderId;
this.processingTime = processingTime;
this.success = success;
}
}
// Usage in application
public class OrderProcessor {
public void processOrder(Order order) {
long startTime = System.nanoTime();
OrderProcessingEvent event = new OrderProcessingEvent(
order.getId(), 0, false
);
event.begin();
try {
// Process order logic
boolean success = businessLogic(order);
event.success = success;
} finally {
event.end();
event.processingTime = System.nanoTime() - startTime;
event.commit();
}
}
}
5. Advanced JFR Configurations
Custom Settings Template
Create custom-profile.jfc:
<?xml version="1.0" encoding="UTF-8"?> <configuration version="2.0"> <event name="jdk.CPULoad"> <setting name="enabled">true</setting> <setting name="period">1 s</setting> </event> <event name="jdk.ActiveSetting"> <setting name="enabled">true</setting> </event> <event name="jdk.GarbageCollection"> <setting name="enabled">true</setting> <setting name="threshold">20 ms</setting> </event> <event name="jdk.ObjectAllocationInNewTLAB"> <setting name="enabled">true</setting> <setting name="threshold">10 kB</setting> </event> <event name="jdk.JavaMonitorWait"> <setting name="enabled">true</setting> <setting name="threshold">20 ms</setting> </event> <!-- Custom business events --> <event name="com.example.BusinessEvent"> <setting name="enabled">true</setting> </event> </configuration>
Programmatic JFR Control
import jdk.jfr.*;
import java.time.Duration;
public class ProgrammaticJFR {
public static void startContinuousRecording() throws Exception {
// Create recording configuration
Configuration config = Configuration.getConfiguration("profile");
Recording recording = new Recording(config);
recording.setName("Programmatic Recording");
recording.setDestination("continuous.jfr");
recording.setMaxAge(Duration.ofHours(1));
recording.setMaxSize(100 * 1024 * 1024); // 100MB
// Start recording
recording.start();
// Schedule stop after 10 minutes
Thread.sleep(Duration.ofMinutes(10).toMillis());
recording.stop();
recording.close();
}
public static void startTimedRecording() {
Recording recording = new Recording();
recording.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
recording.enable("jdk.GarbageCollection").withThreshold(Duration.ofMillis(10));
recording.enable("jdk.ObjectAllocationInNewTLAB").withThreshold(Duration.ofMillis(10));
recording.start();
// Recording will automatically stop and close
recording.scheduleStop(Duration.ofMinutes(5));
}
}
6. Real-World Profiling Scenarios
Scenario 1: High CPU Usage
Symptoms: 100% CPU utilization, slow response times
JFR Analysis Steps:
- Enable recording:
settings=profile - Check "Hot Methods" in Code tab
- Analyze thread states for busy threads
- Look for compilation events indicating hot code
Common Findings:
- Inefficient algorithms
- Tight loops without yielding
- Excessive logging or string operations
Scenario 2: Memory Issues
Symptoms: Frequent GC, OutOfMemoryError, high memory usage
JFR Analysis Steps:
- Monitor GC events for pause times and frequency
- Check allocation pressure in Memory tab
- Analyze object allocation by type and method
- Look for memory leak patterns
// Memory analysis example
public class MemoryAnalysis {
// JFR will show allocation sites and rates
public void processData(List<String> data) {
List<String> results = new ArrayList<>();
for (String item : data) {
// This allocation pattern will be visible in JFR
String processed = item.trim().toUpperCase() + System.currentTimeMillis();
results.add(processed);
}
// Large list retention may cause memory issues
cacheResults(results);
}
}
Scenario 3: Lock Contention
Symptoms: Poor multi-threaded performance, thread blocking
JFR Analysis Steps:
- Enable lock events:
jdk.JavaMonitorEnter - Analyze thread states for BLOCKED threads
- Check "Lock Instances" for contended monitors
- Monitor wait times for synchronization
public class LockContentionExample {
private final Object heavyLock = new Object();
private final Map<String, String> data = new HashMap<>();
public void processConcurrently() {
// This synchronized block may cause contention
synchronized (heavyLock) {
// Long-running operation under lock
expensiveOperation();
data.put("key", "value");
}
}
// JFR will show monitor enter/exit events and wait times
}
7. JFR with Application Servers
Spring Boot Configuration
# Spring Boot with JFR java -XX:+FlightRecorder \ -XX:StartFlightRecording=disk=true,maxsize=100M,name=SpringBootApp \ -jar spring-boot-app.jar
Tomcat Configuration
# Add to catalina.sh or setenv.sh export JAVA_OPTS="$JAVA_OPTS -XX:+FlightRecorder" export JAVA_OPTS="$JAVA_OPTS -XX:FlightRecorderOptions=defaultrecording=true,disk=true,maxsize=1G"
Docker Container Usage
FROM openjdk:11-jre # Copy application COPY myapp.jar /app/myapp.jar # Run with JFR enabled CMD ["java", "-XX:+FlightRecorder", \ "-XX:StartFlightRecording=disk=true,maxage=1h,maxsize=100M", \ "-jar", "/app/myapp.jar"]
8. Automated Analysis and Scripting
JFR Parsing with jfr command
# Parse JFR file from command line jfr print --events jdk.GarbageCollection recording.jfr # Filter specific events jfr print --events jdk.GCPhasePause recording.jfr | grep "pause" # Export to JSON for external processing jfr print --json --events jdk.CPULoad recording.jfr > cpuload.json
Programmatic JFR Analysis
import jdk.jfr.consumer.*;
public class JfrAnalysisTool {
public void analyzeRecording(String filename) throws Exception {
try (RecordingFile recording = new RecordingFile(Paths.get(filename))) {
while (recording.hasMoreEvents()) {
RecordedEvent event = recording.readEvent();
if (event.getEventType().getName().equals("jdk.GarbageCollection")) {
analyzeGCEvent(event);
}
if (event.getEventType().getName().equals("jdk.CPULoad")) {
analyzeCPULoad(event);
}
}
}
}
private void analyzeGCEvent(RecordedEvent event) {
String gcName = event.getString("name");
long duration = event.getLong("duration");
if (duration > 100_000_000) { // 100ms threshold
System.out.printf("Long GC: %s took %d ms%n",
gcName, duration / 1_000_000);
}
}
private void analyzeCPULoad(RecordedEvent event) {
double systemLoad = event.getDouble("machineTotal");
double jvmLoad = event.getDouble("jvmUser");
if (systemLoad > 0.8) {
System.out.printf("High system load: %.2f, JVM: %.2f%n",
systemLoad, jvmLoad);
}
}
}
9. Best Practices for Production Profiling
Safe Production Configuration
# Production-safe JFR settings java -XX:+FlightRecorder \ -XX:FlightRecorderOptions=defaultrecording=true,\ disk=true,\ maxsize=500M,\ maxage=2h,\ repository=./jfr-repository,\ stackdepth=64 \ -jar production-app.jar
Monitoring Strategy
- Continuous Recording: Always run with minimal overhead
- Incident Capture: Increase detail during issues
- Regular Analysis: Weekly performance reviews
- Baseline Comparison: Compare against known good states
Performance Overhead Management
# Balance between detail and overhead # Low overhead (production default) -XX:FlightRecorderOptions=stackdepth=64,threadbuffersize=64k # Higher detail (for troubleshooting) -XX:FlightRecorderOptions=stackdepth=128,threadbuffersize=256k,globalbuffersize=64M
10. Integration with Monitoring Systems
JFR + Grafana/Prometheus
// Export JFR metrics to Prometheus
public class JfrMetricsExporter {
private final CollectorRegistry registry = CollectorRegistry.defaultRegistry;
public void exportGCMetrics(RecordingFile recording) throws IOException {
Gauge gcPauseTime = Gauge.build()
.name("jfr_gc_pause_seconds")
.help("GC pause time from JFR")
.register(registry);
while (recording.hasMoreEvents()) {
RecordedEvent event = recording.readEvent();
if (event.getEventType().getName().equals("jdk.GarbageCollection")) {
double pauseSeconds = event.getLong("duration") / 1_000_000_000.0;
gcPauseTime.set(pauseSeconds);
}
}
}
}
Automated Alerting
#!/bin/bash
# Script to check for performance issues in JFR recordings
check_jfr_recording() {
local recording_file=$1
# Check for long GC pauses
local long_gc_count=$(jfr print --events jdk.GarbageCollection "$recording_file" | \
grep "duration" | awk '{if ($2 > 100000000) print $0}' | wc -l)
# Check for high CPU methods
local hot_methods=$(jfr print --events jdk.ExecutionSample "$recording_file" | \
grep -c "some-hot-method")
if [ "$long_gc_count" -gt 5 ] || [ "$hot_methods" -gt 10 ]; then
echo "ALERT: Performance issues detected in $recording_file"
return 1
fi
return 0
}
Summary
Java Mission Control with Flight Recorder provides:
- Production-safe profiling with minimal overhead
- Deep JVM insights without code modification
- Historical analysis capabilities for trend spotting
- Custom event support for business-specific monitoring
- Rich GUI and command-line analysis tools
Key Benefits:
- Identify performance bottlenecks quickly
- Debug memory leaks and GC issues
- Analyze lock contention and thread problems
- Monitor application behavior in production
- Create performance baselines and track regressions
JMC/JFR should be part of every Java developer's toolkit for performance analysis and troubleshooting in both development and production environments.