Skip to content

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:

cjfr agent myapp start '/var/rec/app_$index.cjfr' --rotating --max-files=10 --max-size=100m

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:

cjfr agent myapp status
cjfr agent myapp stop

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:

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