Java Mission Control: Flight Recordings and Heap Analysis

Complete guide to JDK Mission Control for flight recordings, heap analysis, and identifying hotspot methods in production Java applications.

published: reading time: 20 min read author: GeekWorkBench

Java Mission Control: Flight Recordings and Heap Analysis

Java Mission Control (JMC) is the JDK’s built-in profiling and diagnostics tool that most developers overlook. It ships with the JDK, costs nothing, and provides capabilities that rival expensive commercial profilers. The flight recorder captures low-overhead profiling data in production, while the heap analysis tools dig into memory issues without the overhead of heap dumps.

This is not a flashy tool. The interface looks dated and the learning curve is steep. But when you need to understand what your JVM is actually doing in production, JMC delivers insights that no other tool can match without significant cost or overhead.

Introduction

Java Mission Control (JMC) is the JDK’s built-in profiling and diagnostics tool that most developers overlook. It ships with the JDK, costs nothing, and provides capabilities that rival expensive commercial profilers. The flight recorder captures low-overhead profiling data in production environments where aggressive profiling is not an option. The heap analysis tools dig into memory issues without the latency hit of standalone heap dump tools. The automated analysis view surfaces JVM configuration problems, excessive garbage collection, and lock contention without requiring you to know exactly what to look for. For teams that cannot afford YourKit or JProfiler, JMC is the answer to problems those tools solve—provided you are willing to invest the time to learn its dated interface.

The tool is not flashy. The UI looks like it was designed a decade ago, and in some ways it was—the JMC 8 release that coincided with Java 11 brought significant capabilities but kept much of the original design. The learning curve is real: automated analysis views, memory inspector, code flame graphs, thread state analysis, and heap dump analysis with allocation sites are all powerful but not discoverable. Running the automated analysis first, then diving into specific views, is the workflow that makes sense for most investigations.

This guide covers JMC architecture, how to enable and control flight recordings via JCMD and JMX, and how to use each analysis view effectively. You will learn to correlate flight recorder data with application logs, identify JIT compilation hotspots, diagnose lock contention, and find memory leaks using allocation site information that JMC captures but jmap does not. Production failure scenarios show how JMC data has diagnosed issues ranging from JIT deoptimization to classloader memory leaks that were invisible to other monitoring tools.

When to Use JMC / When Not to Use JMC

JMC excels in production environments where you cannot afford the overhead of aggressive profiling. The flight recorder runs at less than 2% CPU impact even with detailed event capture. It works well for correlating application behavior with specific operations, understanding JIT compilation effectiveness, and identifying lock contention in concurrent applications.

Avoid JMC when you need to profile very short-running applications—the overhead of starting the JVM itself dominates. For quick development-cycle checks during coding, lightweight tools like VisualVM or async-profiler give faster feedback. If you need allocation profiling with object retention details, YourKit or JProfiler offer more intuitive interfaces for that analysis.

JMC also requires JDK distribution rather than JRE, and some organizations struggle with the licensing implications of using Oracle JDK features in production. OpenJDK builds with JMC support exist, but the flight recorder implementation quality varies.

JMC Architecture

graph TD
    subgraph "JMC Client"
        JMCUI[JMC UI<br/>Flight Recorder Browser<br/>Heap Dump Analyzer]
    end

    subgraph "JMX/RMI Connection"
        JMX[JMX MBean Server<br/>Remote JVM Connection]
    end

    subgraph "JVM Internals"
        FR[Flight Recorder<br/>Event Buffer<br/>Continuous/Profiling Mode]
        CP[Code Cache<br/>Compilation Events]
        GC[GC Events<br/>Heap Statistics]
        TH[Thread Events<br/>Allocation Profiling]
    end

    JMCUI <-->|"JMX/RMI"| JMX
    JMX --> FR
    JMX --> CP
    JMX --> GC
    JMX --> TH

The flight recorder runs inside the JVM as a native agent, buffering events in a circular log. When you connect with JMC, it downloads and parses this buffer. For continuous monitoring, events can stream to a flight recording file that you analyze later.

## Enabling Flight Recordings

Flight recordings work through JCMD commands or JMX beans. Here is how to trigger recordings on a running JVM:

```bash
# Check available flight recorder commands
jcmd <pid> JFR.check

# Start a 60-second recording
jcmd <pid> JFR.start name=profile duration=60s filename=profile.jfr

# Start continuous recording with 1GB buffer
jcmd <pid> JFR.start name=continuous settings=profile \
    maxsize=1GB maxage=6h filename=continuous.jfr

# Dump running recording
jcmd <pid> JFR.dump name=profile filename=profile-dump.jfr

# Stop recording
jcmd <pid> JFR.stop name=profile

For JVM startup recordings, add these flags:

# Enable flight recorder on startup
java -XX:StartFlightRecording=\
disk=true,\
dumponexit=true,\
filename=/var/log/myapp.jfr,\
maxsize=512M,\
maxage=2d,\
settings=profile \
-jar your-application.jar

The settings=profile argument uses the built-in profiling template. For lower overhead, use settings=default. You can create custom templates with the JMC GUI.

Analyzing Flight Recordings

Open the .jfr file in JMC. The main views you will use:

Automated Analysis View lists detected problems like excessive garbage collection, JVM configuration issues, and lock contention. This is where to start even before diving into details. The analysis flags things like “High GC frequency” or “Excessive hprof dump size” that correlate with performance issues.

Memory view shows allocation rates, object statistics, and GC causes. Look at the “Allocation in new generation” graph to see if allocation patterns match your expectations. Sudden spikes in allocation often correlate with specific application operations.

Code view displays the hottest methods and call trees. Sort by “CPU Usage” to find methods that consume the most CPU time. The “Compile” events show JIT compilation activity—frequent deoptimization or recompilation indicates unstable JIT optimization.

Thread view reveals thread states, lock contention, and I/O waits. The “Lock Instances” section shows which objects have threads waiting to acquire them. This is invaluable for diagnosing concurrency bottlenecks.

Heap Dump Analysis with JMC

JMC can trigger and analyze heap dumps without the latency hit of tools like jmap. The advantage is that JMC’s heap dump shows allocation sites—where each object was created—which significantly speeds up memory leak diagnosis.

# Trigger heap dump through JMX
jcmd <pid> GC.heap_dump filename=heapdump.hprof

# Analyze in JMC by opening the .hprof file
# Or use jmap for simple histograms
jmap -histo <pid> | head -50

In JMC’s heap dump view, the “Allocations” tab shows the allocation site for each object. This tells you exactly which code path created leaked objects, not just that they exist.

Production Failure Scenarios

Scenario 1: Identifying JIT Compilation Hotspots

A latency-sensitive service shows occasional 100ms+ pauses that do not correlate with GC. Flight recorder data reveals the pauses occur during JIT deoptimization—methods that the JIT compiler optimized aggressively are being reverted. This typically happens when JIT assumptions prove wrong, like classes loading that invalidate type profiles.

The solution involves identifying the affected methods and either fixing the code that triggers deoptimization or adding guards that stabilize JIT behavior.

Scenario 2: Finding Memory Leaks with Allocation Analysis

A service that runs fine for hours starts exhibiting high memory usage and eventually crashes with OutOfMemoryError. Heap dump analysis in JMC shows unexpected classloaders holding references, or a cache implementation growing without bounds.

The allocation site information points directly to the problematic code rather than the symptom location.

Scenario 3: Diagnosing Lock Contention

An application with multiple threads shows low CPU utilization despite high load. Flight recorder data reveals threads spending significant time blocked on locks. The thread view shows which locks are contended and which code paths acquire them.

This points to opportunities for reducing lock granularity or using concurrent data structures.

Trade-off Table

FeatureJMC Flight Recorderasync-profilerYourKit/JProfiler
Overhead2-5% typical1-3%5-15%
Allocation profilingYesYesYes
Allocation sitesYesYesYes
Continuous recordingYesNoNo
Heap dumpsYes (with sites)NoYes
Interface qualityPoorGoodExcellent
CostFree with JDKFreeCommercial

Implementation Snippets

Programmatic Flight Recording Control

import jdk.jfr.*;
import java.lang.reflect.*;

public class FlightRecorderController {

    public static void startRecording(String name, Duration duration)
            throws Exception {
        // Use reflection for newer JFR APIs
        Class<?> flightRecorderClass =
            Class.forName("jdk.jfr.FlightRecorder");
        Method getFlightRecorder =
            flightRecorderClass.getMethod("getFlightRecorder");
        Object recorder = getFlightRecorder.invoke(null);

        Class<?> recordingOptionsBuilderClass =
            Class.forName("jdk.jfr.RecordingOptionsBuilder");
        Object builder = recordingOptionsBuilderClass
            .getConstructor(String.class)
            .newInstance(name);

        Method withDuration =
            recordingOptionsBuilderClass.getMethod("withDuration", Duration.class);
        withDuration.invoke(builder, duration);

        Method build = recordingOptionsBuilderClass.getMethod("build");
        Object options = build.invoke(builder);

        Class<?> flightRecorderClass2 =
            Class.forName("jdk.jfr.FlightRecorder");
        Method startRecording =
            flightRecorderClass2.getMethod("startRecording", options.getClass());
        startRecording.invoke(recorder, options);
    }

    public static void dumpRecording(String filename) throws Exception {
        Class<?> flightRecorderClass =
            Class.forName("jdk.jfr.FlightRecorder");
        Method getFlightRecorder =
            flightRecorderClass.getMethod("getFlightRecorder");
        Object recorder = getFlightRecorder.invoke(null);

        Method stopRecording =
            flightRecorderClass.getMethod("stop", String.class);
        Object recording = stopRecording.invoke(recorder, filename);

        Method dump = recording.getClass().getMethod("dump", String.class);
        dump.invoke(recording, filename);
    }
}

Connecting to JMC Programmatically

import javax.management.*;
import java.lang.management.*;

public class JmcConnection {

    public static void main(String[] args) throws Exception {
        // Connect to local or remote JVM via JMX
        String url = "service:jmx:rmi:///jndi/rmi://localhost:7091/jmxrmi";
        JMXServiceURL serviceUrl = new JMXServiceURL(url);
        JMXConnector connector =
            JMXConnectorFactory.connect(serviceUrl);
        MBeanServerConnection mbsc =
            connector.getMBeanServerConnection();

        // Access flight recorder MXBean
        ObjectName name = new ObjectName(
            "jdk.management.jfr:type=FlightRecorder");
        JmxFlightRecorderMXBean frBean =
            JmxFlightRecorderMXBean.cast(
                mbsc.getObjectInstance(name));

        // Check recording status
        System.out.println("Recordings: " + frBean.getRecordings());
    }
}

Observability Checklist

Before considering profiling complete, verify these points. Flight recordings captured the relevant time window during actual production load. The recording settings matched your information needs—profile settings capture more detail but use more overhead. The JVM had sufficient buffer space—recording data lost to buffer overflow reduces analysis quality. You have matched the production environment closely enough that findings apply.

Also confirm that your JVM flags did not disable JMC or flight recorder, some security policies restrict flight recorder access, and JDK version matches between recording and analysis.

Security and Compliance Notes

Flight recordings contain sensitive data. Method names, class names, and stack traces reveal application internals that you may not want exposed. The string pool in recordings can contain user data, passwords in memory, or other sensitive content.

Restrict JMX access to flight recorder MBeans. Use authentication and TLS for JMX connections in production. Do not transfer flight recordings over untrusted networks without encryption.

Compliance teams may require handling flight recordings as sensitive operational data. Include them in your data classification and handling procedures.

Common Pitfalls / Anti-Patterns

The biggest mistake is using settings=profile when settings=default suffices. The profiling template adds significant overhead and storage costs for data you often do not need. Start with default and only use profile when investigating specific issues.

Another common error is analyzing recordings from unrepresentative periods. A recording captured during application warmup shows different behavior than steady state. Always capture recordings during actual production conditions.

Forgetting to increase the buffer size loses data. With insufficient buffer, old events are discarded before you can download them. Monitor buffer utilization when setting up continuous recording.

Finally, using JMC against a different JDK version than the recording JVM can cause parsing errors or missing data. Always match versions when possible.

Quick Recap Checklist

  • Enable flight recorder with -XX:StartFlightRecording or JCMD
  • Use settings=default for continuous production recording
  • Run automated analysis first to identify obvious issues
  • Use Memory view for allocation patterns, Code view for CPU hotspots
  • Correlate JMC data with application logs and metrics
  • Protect flight recordings—they expose internal application details
  • Match JDK versions between recording and analysis

Interview Questions

1. How does Java Flight Recorder capture profiling data with such low overhead?

Flight recorder uses event-based instrumentation inside the JVM. The JVM already tracks events like method entry, lock acquisition, and object allocation for its own operations. Flight recorder hooks into these existing code paths and writes event records to a circular buffer in native memory.

Because the buffer is in native memory, it does not trigger garbage collection. Events are only recorded if they meet the configured threshold and filter criteria. Most events are sampled rather than captured for every occurrence, keeping overhead minimal. The overhead is typically under 2% even with detailed recording enabled.

2. What is the difference between JMC's heap dump and the jmap heap dump command?

Both produce HPROF-format heap dumps, but JMC's heap dump includes allocation sites—information about where each object was created. This is invaluable for diagnosing memory leaks because you can trace leaked objects back to their creation point rather than just seeing that they exist.

The jmap command triggers a Full GC before dumping, which means objects that would have been collected do not appear in the dump. JMC's dump can optionally avoid this GC pause. For large heaps, jmap can cause longer pauses or even timeouts, while JMC's approach is more controlled.

3. What information does the JMC Code view provide for performance tuning?

The Code view shows which methods consume the most CPU time, organized by call tree. You can see not just that Method X is slow, but which call chain leads to it. The "Top Java Methods" table shows methods ranked by CPU usage. "Top Native Methods" shows native code hotspots. "Exception Check" events indicate exception handling frequency, which sometimes reveals error handling paths executing too often.

Compilation events reveal JIT behavior—methods being compiled, recompiled with different optimization levels, or deoptimized. Frequent deoptimization suggests unstable type profiles or classloading patterns that confuse the JIT compiler.

4. How do you use JMC to diagnose a thread starvation problem?

Look at the Thread view for threads in the BLOCKED or WAITING states. The "Waited Time" and "Blocked Time" charts show which threads spend the most time waiting. The "Lock Instances" section shows exactly which objects have threads blocked on them. From there, trace back to the code that owns those locks.

Check for patterns like many threads waiting on one lock, which suggests lock granularity issues. Look for threads waiting with timeouts versus indefinite waits—timed waits indicate explicit timeout logic, indefinite waits suggest possible deadlocks or connection pool exhaustion.

5. What JVM flags control flight recorder behavior?

Key flags include: -XX:StartFlightRecording enables recording on JVM start with options like delay, duration, filename, maxage, maxsize, and settings. -XX:FlightRecorderOptions configures buffer size, thread filter, and other low-level settings. -XX:+UseFlightRecorder enables flight recorder in older JVMs that require explicit enabling.

The settings option selects a recording template. Built-in templates are default (minimal overhead) and profile (detailed information). You can create custom templates in JMC and reference them by name.

6. What is the difference between the "default" and "profile" JFR settings templates?

The default template captures essential events with minimal overhead—suitable for always-on production recording. It includes basic GC events, compilation events, and exception errors. The profile template adds detailed information including method profiling, object allocation recording, and many more event types.

For continuous production recording, use default to keep overhead under 2%. Use profile only when investigating specific issues where you need the additional detail to understand the problem.

7. How does JMC's Automated Analysis view detect problems?

Automated Analysis uses pattern matching against known problem signatures. It identifies excessive GC frequency, long GC pauses, high allocation rates, JIT compilation issues, lock contention, and many JVM-level problems. Each finding includes the affected time range, estimated performance impact, and a suggested cause.

Run Automated Analysis first before diving into raw event data—it surfaces the most impactful issues quickly and prioritizes what to investigate first.

8. Can you use JFR with containers and Kubernetes?

Yes, JFR works in containerized environments. The JVM automatically detects container memory limits when -XX:+UseContainerSupport is enabled, which affects JFR sizing parameters.

In Kubernetes, direct JFR output to a volume mount so recordings persist beyond pod restarts. Alternatively, stream recording data through JMX using a sidecar container with JMC that connects to the JVM remotely. Ensure network connectivity and appropriate JMX authentication are configured for remote connections.

9. What does the JMC Thread view tell you about lock contention?

The Thread view shows threads organized by state (RUNNING, BLOCKED, WAITING, SLEEPING). The Lock Instances section lists every lock object with waiting threads. For each lock, you see which code path acquired it, which threads are waiting, and for timed waits, the timeout duration.

This helps identify both contention hotspots (which lock is most fought over) and the call patterns that lead to lock acquisition. The flame graph integration shows precisely which call stacks are involved in lock wait.

10. How do you configure JFR for continuous recording with file rotation?

Use -XX:StartFlightRecording=settings=default,maxage=6h,maxsize=1GB,filename=/var/log/app.jfr. The maxage limits how long recordings are kept, and maxsize limits file size. When both limits are reached, old data is discarded and new recording starts.

For continuous recording with rollover, also add dumponexit=true and -XX:ExitOnOutOfMemoryError to capture the recording state at failure.

11. What information does JFR provide about JIT compilation that profiling tools cannot?

JFR records every JIT compilation event: which methods were compiled, the compilation level achieved, whether the code was later deoptimized, and why. It captures "Unknown_ADDRESS" events when compiled code addresses are flushed from memory, and "SweepCodeCache" events showing code cache management.

This detailed JIT visibility helps diagnose deoptimization-related latency spikes, identifies whether warmup is being blocked by specific compilation failures, and reveals opportunities to improve startup by controlling compilation thresholds.

12. What security considerations apply when using JFR in production?

JFR recordings contain sensitive application internals including method names like class names, stack traces, string constants, and potentially user data in the string pool. The data must be treated as highly sensitive operational data.

Restrict JMX access with authentication, use TLS for remote connections, and store recordings in secure locations with appropriate access controls. Avoid enabling settings=profile in security-sensitive environments unless actively investigating an incident.

13. How do you use JFR to diagnose problems in GraalVM Native Image?

GraalVM Native Image does not use a traditional JVM and thus does not support JFR. For Native Image applications, use alternative profiling approaches like async-profiler or GraalVM-specific tools like visualvm with GraalVM plugin. Some GraalVM Enterprise features include proprietary profiling capabilities.

If you need JFR for an application that will be compiled to native image, profile during the JVM phase before native compilation, or design the application to fall back to alternative profilers in production.

14. What is the difference between JFR event granularity and sampling frequency?

JFR records events at various granularities. Some events fire every occurrence (method entry/exit for profiled methods), while others use thresholds (exceptions only when frequency exceeds threshold). The settings=profile template increases sampling frequency across all event types, while settings=default uses thresholds to reduce overhead.

Understanding this distinction helps you tune recordings: raising thresholds reduces overhead but may miss rare events, while lowering thresholds captures more detail at higher CPU cost.

15. How does JFR handle data retention and storage in continuous recording?

Continuous recording with maxage or maxsize limits how much historical data is retained. When the limit is reached, oldest events are discarded to make room for new ones. The recording maintains a circular buffer of events up to the configured age or size.

For incident investigation, you need to dump a recording before it fills up. The jcmd JFR.dump command extracts a specific time window from a running recording without stopping it.

16. What do the JFR Memory view "Allocation in new generation" charts reveal?

The "Allocation in new generation" graph shows the rate at which objects are allocated in the young generation. Sudden spikes indicate specific application operations that allocate heavily. Comparing allocation patterns with application behavior (batch jobs, user requests) helps identify optimization targets.

High allocation rates correlate with GC pressure. If you see allocation spikes followed by frequent minor GCs, reducing allocation in hot paths can significantly improve performance.

17. How do you Correlate JFR events with application logs?

Use the timestamp or uptime from JFR events to match against application log entries. JFR events include UTC timestamps that align with standard logging timestamps. In JMC, right-click any event and select "Copy as JShell command" to capture the event details including timing.

This correlation is essential for diagnosing incidents where you need to understand what the application was doing when a JFR-detected issue occurred.

18. What is the "Environment" tab in JMC and what does it show?

The Environment tab shows JVM and system configuration at the time of the recording. It includes JVM flags in effect, Java version and vendor, OS version, CPU count, and container memory limits if applicable. This context is critical for reproducing issues—you need to know what settings were active.

Always capture this information when filing bug reports or investigating issues, as the same application can behave differently under different JVM configurations.

19. How do you identify memory leaks using JFR's Memory view?

Look for a pattern where heap usage grows continuously over time without corresponding decreases after GC events. The "Heap" graph shows used vs. committed memory. The "GC Causes" graph shows which GC events occurred. If you see constant Full GCs followed by heap not decreasing significantly, you have a memory leak.

Use the allocation profiling in the Memory view to see which code paths allocate the most memory, then trace what should be holding onto that memory.

20. What is the difference between JFR commercial features and open JDK features?

Flight Recorder was a commercial feature in Oracle JDK until Java 11 when it was open-sourced under the GPL. In OpenJDK and AdoptOpenJDK, JFR is fully available. Oracle JDK may have additional commercial features, but core JFR functionality is identical across distributions.

For production use, verify your JDK distribution includes JFR support and check licensing terms. Most production-friendly distributions include full JFR support.

Further Reading

Conclusion

Java Mission Control and Java Flight Recorder together provide production-safe profiling with minimal overhead. Enable flight recorder with -XX:StartFlightRecording or through JCMD for continuous monitoring. Use the automated analysis view first to identify obvious issues, then dive into Memory, Code, and Thread views for detailed investigation.

JMC excels at correlating application behavior with JVM internals like JIT compilation, GC activity, and lock contention. The built-in nature of JFR means it works without additional agent installation. For performance troubleshooting in production, JMC and JFR should be your first tools before reaching for third-party profilers with higher overhead.

Category

Related Posts

Deoptimization Debugging: When JIT Compiled Code Reverts

Learn what causes the JVM to deoptimize JIT-compiled code, how to detect deoptimization events, and how to fix the underlying issues.

#jvm #jit #deoptimization

Java Flight Recorder: Continuous Monitoring and Diagnostics

Learn how Java Flight Recorder captures low-level diagnostics, profiling data, and continuous monitoring events from the JVM in production environments.

#jvm #java #profiling

JVM Flags Explained: Standard, -X, and -XX Options for Tuning

Master JVM flags configuration with this comprehensive guide covering standard, -X, and -XX options for production Java performance tuning.

#jvm #performance #tuning