Common Workflows¶
Practical recipes for the most common cjfr use cases.
Each workflow is self-contained; copy the commands, substitute your paths.
Continuous GC recording on a server¶
The most common production setup: a rolling window of GC data with bounded disk usage.
# Keep last 5 × 100 MB ≈ 500 MB on disk at all times
java -javaagent:cjfr.jar='start,/var/rec/app_$index.cjfr,rotating,max-files=5,max-size=100m' \
-jar myapp.jar
Or attach to an already-running process:
Single-quote the path
When the output path contains $index or $date, always single-quote it
in shell to prevent expansion: '/var/rec/app_$index.cjfr', not
"/var/rec/app_$index.cjfr".
To verify it is running:
Recording on a server, analysing locally¶
For the full fleet recording workflow; server-side agent setup, transferring files, and running offline analysis; see Production Recording and the Fleet-Wide Monitoring Cookbook.
Inflating multiple files, keeping only a time range¶
A common post-incident workflow: you have several rotation files spanning many hours, and you want a single JFR covering just the 30 minutes around the incident.
# Step 1: find out when the recording starts
cjfr summary --short app_0.cjfr app_1.cjfr app_2.cjfr
# Step 2: extract the window across all files into a single JFR
cjfr inflate \
--start="2024-05-24 14:25:00" --duration=30m \
app_0.cjfr app_1.cjfr app_2.cjfr \
incident.jfr
# Step 3: open in a JFR viewer; JDK Mission Control, Firefox Profiler, or jfr-query
jmc incident.jfr
The time-range filter is applied across the merged file set; you don't need to know which rotation file contains the incident window.
Producing a JFR slice (time range)¶
Extract a specific window from a recording without loading the whole file into Mission Control.
# 5-minute slice around an incident at 14:32
cjfr inflate --start="2024-05-24 14:30:00" --duration=5m recording.cjfr incident.jfr
# First 10 minutes of a recording
cjfr inflate --start="2024-05-24 12:06:42" --duration=10m recording.cjfr warmup.jfr
Timestamps must include a date. Use cjfr summary --short to find the recording's
start time, then build your window from there.
Extracting only GC events¶
Produce a minimal JFR containing just what you need for GC analysis; much smaller than a full inflation, and faster to open in Mission Control.
# Inflate to a GC-only JFR
cjfr inflate \
--events=jdk.GarbageCollection,jdk.GCHeapSummary,jdk.G1HeapSummary,\
jdk.MetaspaceSummary,jdk.GCPhasePause,jdk.TenuringDistribution,\
jdk.GCReferenceStatistics,jdk.GCCPUTime \
recording.cjfr gc-only.jfr
# Quick GC summary without inflating at all
cjfr summary recording.cjfr
# Zoom into the worst pause spikes and the 1-minute context around them
cjfr summary --gc-percentile=95 recording.cjfr
cjfr inflate --gc-percentile=95 recording.cjfr worst-pauses.jfr
Merging multiple rotating files¶
When a recording spans several rotation files, combine them for analysis:
# Summarise the whole day
cjfr summary app_0.cjfr app_1.cjfr app_2.cjfr app_3.cjfr
# Or use a glob; the shell expands it
cjfr summary app_*.cjfr
# Merge into a single .jfr for tooling that reads JFR directly
cjfr inflate app_0.cjfr app_1.cjfr app_2.cjfr archive.jfr
Keeping only a time range from a long recording¶
A long continuous recording only has a narrow interesting window. Extract it and delete the rest:
# Step 1: find the start time
cjfr summary --short recording.cjfr
# Step 2: extract the 30-minute window around the event
cjfr inflate --start="2024-05-24 14:25:00" --duration=30m recording.cjfr window.jfr
# Step 3 (optional): re-condense back to .cjfr for long-term storage
cjfr condense --condenser-config=default window.jfr window.cjfr
Re-condensing an inflated recording loses additional precision
If the original .cjfr was recorded with default (millisecond
timestamps, 32-frame stacks), inflating it produces a .jfr that already
reflects those reductions. Re-condensing that .jfr applies the same reductions
again: no extra data is lost (there is nothing left to lose), but you do not
recover what was already discarded. If you need the sliced window at full
original precision, go back to the original .cjfr and slice it directly with
cjfr inflate --start=... --duration=... without re-condensing.
Condensing a folder of JFR files¶
condense accepts a directory path and merges all .jfr files it finds into
a single .cjfr alongside the folder:
cjfr condense recordings/
# produces recordings.cjfr
cjfr condense --condenser-config=default recordings/
# Smallest files for cold storage (reduced + MAX_COMPRESSION):
cjfr condense --condenser-config=reduced --compression-level=MAX_COMPRESSION recordings/
Condensing a ZIP archive¶
A ZIP containing .jfr files is treated the same as a folder:
Inspecting what is in a recording¶
# Full event count breakdown
cjfr summary recording.cjfr
# What takes up the most bytes? (opens as an interactive flamegraph in a browser)
cjfr summary --flamegraph storage.html recording.cjfr
open storage.html # or xdg-open on Linux
# Show the first 20 full GC events (view/event name first, then the file)
cjfr view --limit=20 jdk.GarbageCollection recording.cjfr
# Same, as JSON (pipe to jq for scripting)
cjfr view --json --limit=20 jdk.GarbageCollection recording.cjfr \
| jq '.[] | {time: .startTime, cause: .cause, pause: .longestPause}'
# Look at heap size over time (all GC heap summaries)
cjfr view jdk.GCHeapSummary recording.cjfr
# Render a JDK named view (e.g. GC pauses, hot methods) directly on a .cjfr
cjfr view gc-pauses recording.cjfr
cjfr view hot-methods recording.cjfr
All of the JDK's named views work: cjfr reads the running JVM's own view.ini
(from the jrt: runtime image), so the available views match the JDK you run it on.
See Analyzing Recordings for details.
Changing limits on a running recording¶
You can tune rotation settings while the agent is recording; no restart needed:
cjfr agent myapp set-max-files 20 # expand ring buffer after disk expansion
cjfr agent myapp set-max-size 200m # grow per-file cap
cjfr agent myapp set-max-duration 15m # change rotation interval
cjfr agent myapp set-duration 4h # cap total recording length
Choosing the right JAR for your platform¶
To select the smallest JAR that fits your deployment (platform-specific, inflaterless, or minimal), see JAR Release Selection.