Java Mission Control (JMC) for Profiling in Java

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:

  1. Record with settings=profile
  2. Analyze GC and Allocation events
  3. Look for Old Generation growth
  4. 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:

  1. Enable recording: settings=profile
  2. Check "Hot Methods" in Code tab
  3. Analyze thread states for busy threads
  4. 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:

  1. Monitor GC events for pause times and frequency
  2. Check allocation pressure in Memory tab
  3. Analyze object allocation by type and method
  4. 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:

  1. Enable lock events: jdk.JavaMonitorEnter
  2. Analyze thread states for BLOCKED threads
  3. Check "Lock Instances" for contended monitors
  4. 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

  1. Continuous Recording: Always run with minimal overhead
  2. Incident Capture: Increase detail during issues
  3. Regular Analysis: Weekly performance reviews
  4. 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.

Leave a Reply

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


Macro Nepal Helper