Getting Started with cjfr¶
cjfr records JFR data directly to a compact .cjfr format designed for
continuous GC profiling in production. The core workflow is a rotating
ring-buffer on each server: the agent writes successive .cjfr files, evicts
the oldest when the cap is hit, and you always have the last N hours of GC history
on disk at a bounded, predictable storage cost.
Rotating recordings, offline summary without inflation, and targeted JFR slices for JDK Mission Control are the three operations this tool is built around.
Installation¶
Download the latest JAR from GitHub Releases:
curl -L -o cjfr.jar https://github.com/parttimenerd/condensed-data/releases/latest/download/condensed-data.jar
alias cjfr='java -jar '"$(pwd)"'/cjfr.jar'
Requires JDK 17+. Or build from source:
git clone --recurse-submodules https://github.com/parttimenerd/condensed-data.git
cd condensed-data && mvn package -DskipTests
# JAR is at target/condensed-data.jar
Condensing Existing JFR Files¶
If you already have .jfr recordings, convert them to .cjfr:
# Single file
cjfr condense recording.jfr recording.cjfr
# A whole folder
cjfr condense recordings/
# produces recordings.cjfr
# With explicit config (default is lossless; 'default' preset trades a bit of
# precision for 2-4× smaller files on top of LZ4)
cjfr condense --condenser-config=default recording.jfr recording.cjfr
See Configuration Reference for the available presets.
Continuous Rotating Recording (the main use case)¶
Start a rotating GC recording with the agent; this is the one command most production deployments run:
# Keep last 10 × 100 MB ≈ 1 GB of GC history
java -javaagent:cjfr.jar='start,/var/rec/app_$index.cjfr,rotating,max-files=10,max-size=100m' \
-jar myapp.jar
Or attach to an already-running process without restart:
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".
Check status or stop at any time:
See Production Recording Guide for rotation knobs,
live tuning, storage sizing, $index vs $date path placeholders, and JFR
config options.
Analysing a Recording¶
Inspect directly without inflating; fast and JMC-free:
# Summary: event counts, GC pause stats, allocation rate
cjfr summary recording.cjfr
# Context around the worst 10% of pauses (1-minute window per qualifying GC)
cjfr summary --gc-percentile=90 recording.cjfr
# Summarise across multiple rotation files at once
cjfr summary app_0.cjfr app_1.cjfr app_2.cjfr
View named views or individual event types as a table — a drop-in for jfr view:
# Named views from the running JDK's view.ini
cjfr view gc-pauses recording.cjfr
cjfr view hot-methods recording.cjfr
# Single event type (all GC events)
cjfr view jdk.GarbageCollection recording.cjfr
# As JSON for scripting
cjfr view --json jdk.GarbageCollection recording.cjfr | jq '.[] | {cause, pause: .longestPause}'
Print raw events in jfr print format — a drop-in for jfr print:
# All events (same text format as jfr print)
cjfr print recording.cjfr
# Filter by event type or category
cjfr print --events GCPhaseParallel recording.cjfr
cjfr print --categories GC recording.cjfr
# JSON output, full-precision numbers, or limited stack depth
cjfr print --json recording.cjfr
cjfr print --exact recording.cjfr # nanosecond timestamps, unrounded values
cjfr print --stack-depth 5 recording.cjfr
Inflate to .jfr for JDK Mission Control or other JFR viewers:
# Full inflation
cjfr inflate recording.cjfr
# Just a 30-minute window; much faster to open in JMC
cjfr inflate --start="2024-05-24 14:25:00" --duration=30m recording.cjfr incident.jfr
# Only events around the worst GC pauses
cjfr inflate --gc-percentile=95 recording.cjfr worst-pauses.jfr
Inflated .jfr files can be opened in:
- JDK Mission Control: full-featured JFR analysis GUI
- Firefox Profiler: web-based, no install, good for flame graphs
- jfr-query: web-based JFR query tool
Or open .cjfr files directly — no inflation needed — using the
JMC fork that has native .cjfr support built in.
Download a snapshot build from its
releases page.
See Analyzing Recordings for time filters, event filters, and multi-file queries.
Troubleshooting¶
cjfr inflate fails or produces an empty JFR
Make sure you are using the full JAR (not an inflaterless variant) and JDK 17+.
Inflaterless JARs are labelled *-inflaterless* in the filename.
Agent attaches but nothing is recorded
JFR is enabled by default on JDK 11+; older JVMs may need -XX:+FlightRecorder.
Run cjfr agent <pid> status to confirm the recording started.
Output files are larger than expected
The agent uses default condensing with LZ4FRAMED compression by
default. If that is still too large, switch to reduced for more aggressive
event reduction (~1–11% of the raw JFR), or add --compression=GZIP for a better
byte-level ratio at the cost of slower writes. Both changes are independent.
See Configuration Reference.
Further Reading¶
- Production Recording Guide: rotation knobs, live tuning, storage sizing, JFR config
- Configuration Reference: condenser configs and compression algorithms
- Analyzing Recordings: time filters, GC percentile, event filters, multi-file queries
- Common Workflows: end-to-end recipes including condensing existing JFR files
- Cookbooks: GC regression hunt, fleet monitoring, container deployment, archival
- JAR Release Selection: pick the right JAR variant for your deployment