Mastering the JFR View Command: A Comprehensive Guide to Java Flight Recorder Analysis
Java Flight Recorder (JFR) is a low-overhead profiling tool included in the JDK since Java 11 (GA). It captures events—structured data about JVM and application behavior—such as:
- Garbage Collection (GC) cycles
- CPU usage
- Method execution times
- Thread activity
- HTTP requests (for JDK 11+)
The jfr command-line tool manages JFR recordings (start, stop, dump), and the view subcommand analyzes recordings—either from a file or a live JVM. Unlike Java Mission Control (JMC), jfr view is:
- Headless: Works in environments without a GUI (e.g., servers, containers).
- Scriptable: Integrates with shell scripts, CI/CD pipelines, or monitoring tools.
- Lightweight: No extra dependencies—uses only the JDK.
Key use cases for jfr view:
- Troubleshooting performance bottlenecks (CPU, GC, I/O).
- Monitoring live applications in real time.
- Automating analysis with scripts.
- Generating visualizations (flame graphs) for sharing.
Java Flight Recorder (JFR) is a low-overhead profiling and monitoring tool built into the JDK. It captures detailed runtime data (e.g., CPU usage, GC events, thread activity) with minimal impact on application performance—making it ideal for production environments. While Java Mission Control (JMC) provides a graphical interface for JFR analysis, the jfr view command-line tool offers a powerful, scriptable alternative for headless environments or automation.
This blog dives deep into the jfr view command: its key options, advanced usage techniques, best practices, and troubleshooting workflows. By the end, you'll be able to analyze JFR recordings like a pro—whether you're debugging a production issue or optimizing performance.
Table of Contents#
- Introduction to JFR and the View Command
- Prerequisites
- Installing and Accessing JFR
- Core Options of
jfr view4.1. Filtering and Querying Events 4.2. Field Selection and Formatting 4.3. Sorting and Limiting Results - Generating Flame Graphs with JFR Data
- Advanced Usage Techniques 6.1. Combining Filters and Expressions 6.2. Exporting Data to Structured Formats 6.3. Integrating with External Tools
- Best Practices for Effective JFR View Usage
- Common Pitfalls and How to Avoid Them
- Step-by-Step Troubleshooting Example
- Conclusion
- References
2. Prerequisites#
To use jfr view, you need:
- JDK 11 or later: JFR is GA in Java 11; newer versions (Java 17+) add subcommands like
flamegraph. - A Java application: To record and analyze.
- Shell access:
jfr viewis a command-line tool (works on Linux, macOS, Windows).
Verify your setup:
# Check JFR version (JDK 17+)
jfr --version
# Output: openjdk 17.0.8 2023-07-18 LTS
# OpenJDK Runtime Environment Zulu17.44+15-CA (build 17.0.8+7-LTS)
# OpenJDK 64-Bit Server VM Zulu17.44+15-CA (build 17.0.8+7-LTS, mixed mode, sharing)
# Check if JFR is available
jfr help view
# Output: Displays help for the "view" subcommand.3. Installing and Accessing JFR#
JFR is included in all modern JDK distributions (Oracle JDK, OpenJDK, Azul Zulu, Amazon Corretto). No extra installation is needed—just install a JDK and add it to your PATH.
For example, on Ubuntu:
# Install OpenJDK 17
sudo apt update && sudo apt install openjdk-17-jdk
# Verify JFR is present
jfr --help4. Core Subcommands of jfr view#
The jfr view command has several subcommands, each designed for a specific analysis task. We’ll cover the most useful ones below.
4.1. events: Query and Filter Raw Event Data#
The events subcommand queries raw event data from a recording. Use it to:
- Filter events by type, duration, or custom criteria.
- Select specific fields (e.g.,
startTime,duration,method). - Sort and limit results to avoid data overload.
Syntax#
jfr view events [options] <recording-file | pid>Key Options#
| Option | Description |
|---|---|
--filter | Filter events using a boolean expression (e.g., eventType == 'jdk.GarbageCollection'). |
--fields | Specify which fields to display (e.g., --fields "eventType, startTime, duration"). |
--sort | Sort results by a field (e.g., --sort "duration desc" for longest first). |
--limit | Limit the number of results (e.g., --limit 100). |
--format | Output format: table (default), csv, json. |
Example: Filter Slow GC Events#
Suppose you have a recording myapp.jfr and want to find GC events longer than 10ms:
jfr view events \
--filter "eventType == 'jdk.GarbageCollection' and duration > 10ms" \
--fields "eventType, startTime, duration, gcType" \
--sort "duration desc" \
--limit 20 \
myapp.jfrOutput Explanation#
| Event Type | Start Time | Duration | GC Type |
|---|---|---|---|
| jdk.GarbageCollection | 2024-05-20T14:30:00 | 15ms | Old |
| jdk.GarbageCollection | 2024-05-20T14:31:15 | 12ms | Young |
| ... | ... | ... | ... |
This output shows:
eventType: The type of event (GC in this case).startTime: When the event occurred.duration: How long the event lasted.gcType: Whether it’s a Young (minor) or Old (major) GC.
4.2. flamegraph: Visualize CPU and Method Execution#
Flame graphs are interactive visualizations of stack traces—they show which methods consume the most CPU time. Key features:
- Bars: Each bar represents a method.
- Width: Proportional to the CPU time spent in the method.
- Stack Depth: Top bars are currently executing methods; lower bars are parent methods.
The flamegraph subcommand generates an SVG file you can open in a browser.
Syntax#
jfr view flamegraph [options] <recording-file | pid>Key Options#
| Option | Description |
|---|---|
--event | Event type to use (default: jdk.ExecutionSample for CPU). |
--output | Save the flame graph to an SVG file (e.g., --output flame.svg). |
--max-depth | Limit stack depth (e.g., --max-depth 20 to avoid clutter). |
--width | Set SVG width (default: 1200 pixels). |
Example: Generate a CPU Flame Graph#
To find hot methods in myapp.jfr:
jfr view flamegraph \
--event jdk.ExecutionSample \
--output cpu-flamegraph.svg \
myapp.jfrOpen cpu-flamegraph.svg in a browser:
- Wide bars: Methods with high CPU usage (e.g.,
com.myapp.ProcessData.calculate()). - Zoom: Click a bar to zoom in on its sub-methods.
- Tooltips: Hover over a bar to see the method name and CPU time.
Common Event Types for Flame Graphs#
| Event Type | Use Case |
|---|---|
jdk.ExecutionSample | CPU usage (default). |
jdk.ThreadPark | Blocked threads (e.g., waiting for locks). |
jdk.SocketRead | I/O waits (e.g., reading from a socket). |
4.3. summary: High-Level Recording Overview#
The summary subcommand provides a quick snapshot of the recording—use it first to identify areas to investigate.
Syntax#
jfr view summary <recording-file | pid>Example Output#
Recording Summary
=================
File: myapp.jfr
Duration: 00:05:00
Total Events: 123,456
Start Time: 2024-05-20T14:30:00
End Time: 2024-05-20T14:35:00
Event Types by Count
--------------------
jdk.ThreadPark: 45,678 (37%)
jdk.ExecutionSample: 34,567 (28%)
jdk.GarbageCollection: 12,345 (10%)
jdk.HttpClientRequest: 8,765 (7%)
GC Summary
----------
Total GC Time: 15s (5% of recording duration)
Young GC: 100 events (avg 100ms)
Old GC: 10 events (avg 500ms)
Heap Usage
----------
Max Heap: 4GB
Peak Used Heap: 3.2GB (80%)
Average Used Heap: 2.1GB (52%)
Thread Summary
--------------
Total Threads: 50
Peak Threads: 60
Daemon Threads: 10
Key Takeaways from the Summary#
- High GC Time: 5% of total time—investigate with
eventsorflamegraph. - Peak Heap: 80% of max—might need more heap (
-Xmx). - Top Events:
jdk.ThreadPark(37%)—check for blocked threads.
4.4. continuous: Real-Time Monitoring#
The continuous subcommand monitors a live JVM and displays events as they happen. It’s ideal for:
- Debugging intermittent issues (e.g., sudden CPU spikes).
- Monitoring production apps without stopping them.
Syntax#
jfr view continuous [options] <pid>Key Options#
| Option | Description |
|---|---|
--event | Events to monitor (e.g., --event jdk.CPULoad --event jdk.ThreadStart). |
--interval | Update interval (e.g., --interval 1s for 1-second updates). |
--fields | Specify fields to show (e.g., --fields "eventType, startTime, value"). |
Example: Monitor CPU Load and Threads#
To monitor PID 1234 (a running Java app):
jfr view continuous \
--event jdk.CPULoad \
--event jdk.ThreadStart \
--event jdk.ThreadEnd \
--interval 1s \
1234Output#
2024-05-20T14:40:00 - jdk.CPULoad: 45%
2024-05-20T14:40:01 - jdk.ThreadStart: Thread-12 (daemon)
2024-05-20T14:40:02 - jdk.CPULoad: 55%
2024-05-20T14:40:03 - jdk.ThreadEnd: Thread-10 (non-daemon)
Best Practice for Production#
Use continuous sparingly—it has low overhead (~1% CPU), but frequent updates can generate noise. Limit to 1–5 second intervals.
4.5. metadata: Explore Event Schema#
The metadata subcommand documents the event schema—what events are present, their fields, and data types. Use it to:
- Avoid guesswork when filtering events.
- Understand what data is available.
Syntax#
jfr view metadata [options] <recording-file | pid>Key Options#
| Option | Description |
|---|---|
--event-type | Show details for a specific event (e.g., --event-type jdk.GarbageCollection). |
--list | List all event types in the recording. |
Example: Explore GC Event Fields#
To see fields for jdk.GarbageCollection:
jfr view metadata --event-type jdk.GarbageCollection myapp.jfrOutput#
Event Type: jdk.GarbageCollection
--------------------------------
Description: Garbage collection event.
Fields:
- eventType: String (event type name)
- startTime: Instant (event start time)
- duration: Duration (event duration)
- gcType: String (Young/Old)
- heapBefore: MemoryUsage (heap size before GC)
- heapAfter: MemoryUsage (heap size after GC)
- threads: int (number of threads involved)
Why This Matters#
If you try to filter by gcDuration (a non-existent field), jfr view events will fail. metadata ensures you use valid fields.
5. Advanced Usage Techniques#
Now that you know the core subcommands, let’s explore advanced workflows.
5.1. Combining Filters and Expressions#
Use logical operators (and, or, not) and comparisons (>, <, ==, !=) to create precise filters.
Example: Filter Slow, Failed HTTP Requests#
Find HTTP requests that:
- Took longer than 500ms or
- Returned a 4xx/5xx status code.
jfr view events \
--filter "eventType == 'jdk.HttpClientRequest' and (duration > 500ms or statusCode >= 400)" \
--fields "eventType, startTime, duration, statusCode, uri" \
--sort "duration desc" \
myapp.jfrSupported Operators#
| Category | Operators |
|---|---|
| Logical | and, or, not |
| Comparison | ==, !=, >, <, >=, <= |
| String | startsWith(), endsWith(), contains() |
5.2. Exporting Data to Structured Formats#
jfr view events supports csv and json formats—export data to analyze with tools like Excel, Pandas, or Grafana.
Example: Export GC Data to CSV#
jfr view events \
--filter "eventType == 'jdk.GarbageCollection'" \
--fields "eventType, startTime, duration, gcType" \
--format csv \
myapp.jfr > gc-data.csvOpen gc-data.csv in Excel to:
- Create charts (e.g., GC duration over time).
- Calculate averages (e.g., average Old GC duration).
Example: Export CPU Load to JSON#
jfr view events \
--filter "eventType == 'jdk.CPULoad'" \
--fields "startTime, value" \
--format json \
myapp.jfr > cpu-load.jsonUse jq (a JSON processor) to extract values:
cat cpu-load.json | jq '.[] | .value'
# Output: 0.45, 0.52, 0.38, ...5.3. Integrating with External Tools#
jfr view integrates with:
- Shell scripts: Automate analysis (e.g., alert if GC time exceeds 10%).
- Monitoring tools: Send CSV/JSON data to Grafana, Prometheus, or Elasticsearch.
- Log aggregators: Pipe output to Logstash or Fluentd.
Example: Alert on High GC Time#
Create a script check-gc.sh to alert if GC time exceeds 10%:
#!/bin/bash
# Get total GC time from summary
gc_time=$(jfr view summary myapp.jfr | grep "Total GC Time" | awk '{print $4}')
recording_duration=$(jfr view summary myapp.jfr | grep "Duration" | awk '{print $2}')
# Calculate GC percentage (assuming duration is in "mm:ss" format)
gc_seconds=$(echo $gc_time | awk -F: '{print $1*60 + $2}')
recording_seconds=$(echo $recording_duration | awk -F: '{print $1*60 + $2}')
gc_percent=$((gc_seconds * 100 / recording_seconds))
if [ $gc_percent -gt 10 ]; then
echo "ALERT: GC time exceeds 10% ($gc_percent%)"
exit 1
else
echo "GC time is within limits ($gc_percent%)"
exit 0
fiRun the script:
chmod +x check-gc.sh
./check-gc.sh
# Output: ALERT: GC time exceeds 10% (15%)6. Best Practices for Effective JFR View Usage#
Follow these rules to get the most out of jfr view:
- Start with
summary: Get a high-level overview before diving into details. - Filter aggressively: Use
--filteror--limitto avoid data overload. - Use the right event type:
- CPU:
jdk.ExecutionSample(flame graph). - GC:
jdk.GarbageCollection(events). - I/O:
jdk.SocketReadorjdk.FileWrite(events).
- CPU:
- Real-time monitoring: Use
continuoussparingly on production—limit to 1–5 second intervals. - Document your workflow: Save filter commands in scripts for repeatability.
- Combine with JMC: Use
jfr viewfor headless analysis; use JMC for deep dives (e.g., thread dumps, heap dumps).
7. Common Pitfalls and How to Avoid Them#
Pitfall 1: Not Filtering Events#
Problem: jfr view events myapp.jfr returns 100,000 results—impossible to analyze.
Solution: Always use --filter or --limit to narrow down results.
Pitfall 2: Misinterpreting Flame Graphs#
Problem: A wide bar means high CPU time, not more invocations.
Solution: Use jfr view events to check invocation counts:
jfr view events \
--filter "eventType == 'jdk.MethodExecution' and method == 'com.myapp.HotMethod()'" \
--fields "invocationCount" \
myapp.jfrPitfall 3: Ignoring Event Metadata#
Problem: Using invalid fields in filters (e.g., gcDuration instead of duration).
Solution: Run jfr view metadata --event-type <type> first to see valid fields.
Pitfall 4: Viewing Incomplete Recordings#
Problem: jfr view events myapp.jfr fails because the recording wasn’t stopped.
Solution: Stop the recording before viewing:
jfr stop --filename myapp.jfr <pid>Pitfall 5: Overusing Real-Time Monitoring#
Problem: jfr view continuous uses too many resources on production.
Solution: Limit to 1–5 second intervals and monitor only critical events (e.g., jdk.CPULoad).
8. Step-by-Step Troubleshooting Example#
Let’s walk through troubleshooting a slow Java app with jfr view.
Problem#
Your app takes 10 seconds to process a request—significantly slower than expected.
Step 1: Record the App#
Start a 30-second recording of PID 1234:
jfr start --duration 30s --filename slow-app.jfr 1234Step 2: Get an Overview with summary#
jfr view summary slow-app.jfrKey findings:
- Total GC Time: 5s (17% of recording duration).
- Peak Heap: 3.8GB (95% of 4GB max).
Step 3: Dive into GC Events with events#
Filter GC events longer than 10ms:
jfr view events \
--filter "eventType == 'jdk.GarbageCollection' and duration > 10ms" \
--fields "eventType, startTime, duration, gcType, heapBefore.used, heapAfter.used" \
--sort "duration desc" \
slow-app.jfrOutput shows:
- Old GC events (major GC) taking 500ms each.
- Heap before GC: 3.7GB (92% of max).
Step 4: Verify Heap Usage with events#
Check heap usage over time:
jfr view events \
--filter "eventType == 'jdk.HeapUsage'" \
--fields "startTime, used" \
--sort "startTime" \
slow-app.jfrThe used heap increases steadily to 3.8GB—the app is running out of heap.
Step 5: Fix the Issue#
Increase the maximum heap size from 4GB to 8GB:
java -Xmx8g -jar myapp.jarStep 6: Verify the Fix#
Record again and check the summary:
jfr start --duration 30s --filename fixed-app.jfr 5678
jfr view summary fixed-app.jfrKey improvements:
- Total GC Time: 1s (3% of recording duration).
- Peak Heap: 4.2GB (52% of 8GB max).
9. Conclusion#
jfr view is a powerful, flexible tool for analyzing Java applications. Whether you’re troubleshooting a production issue, monitoring a live app, or automating analysis, jfr view provides the tools you need—without a GUI.
Key takeaways:
- Use
summaryfirst to get an overview. - Filter aggressively with
eventsto avoid data overload. - Generate flame graphs with
flamegraphto find hot methods. - Monitor live apps with
continuous(sparingly on production). - Use
metadatato understand event fields.
With practice, jfr view will become your go-to tool for Java performance analysis.
10. References#
- OpenJDK JFR Documentation
- JFR Man Page
- Flame Graphs by Brendan Gregg
- JDK 17 JFR View Command Reference
- jq JSON Processor
- Java Mission Control (JMC)
Let me know if you'd like to dive deeper into any of these topics! 🚀