JFR Advanced
Purpose
Design a recording that can answer a declared question at a measured cost, and prove that the result contains the expected event population. JFR configuration is an observation contract: disabled events, thresholds, periods, throttles, stack settings, chunk retention, concurrent recordings, and consumer loss determine what absence or width can mean.
JFR evolves with the JDK. Event names, fields, annotations, settings, built-in configurations, views, command syntax, platform support, and implementation behavior must be discovered on the target build. Examples here are patterns, not a frozen JDK event catalogue.
Ownership boundary
jfr-and-async-profilerchooses a first capture and production baseline.- This skill owns JFR configuration, APIs, event schema, custom events, and consumers.
continuous-profilingowns fleet collection/storage/retention/query operations.async-profiler-advancedowns async-profiler's JFR producer/events/converter.- Domain skills interpret GC, allocation, locks, I/O, JIT, and virtual-thread events.
Evidence contract
Before changing settings, write:
hypothesis and decision:
target JDK vendor/build/platform and process:
required event types, fields, relation, and weight/duration semantics:
smallest duration/rate/contribution that must remain observable:
eligible threads/tasks/objects and known blind spots:
period/threshold/throttle/stack/filter settings:
duration, chunk/repository/file/retention budget:
CPU/allocation/latency/file-size and consumer-lag budget:
expected event count/rate and validation workload:
privacy/access/retention classification:
Do not lower every threshold because “more data is better.” The useful question is whether the configuration has sufficient coverage and power for the target mechanism while staying inside the production budget.
Discover before configuring
Use the exact runtime/tools:
java -version
jcmd <pid> help JFR.start
jcmd <pid> JFR.check
jfr metadata [recording.jfr]
jfr configure --interactive
jfr help view
jfr summary recording.jfr
Programmatically inspect FlightRecorder.getFlightRecorder().getEventTypes(), EventType.getFields(),
getSettingDescriptors(), and annotations. A string-based enable(name) can create settings
without proving a matching event is currently registered or will emit. Validate registration,
effective active settings, and a positive-control event count.
Metadata in one file/build is not a contract for another vendor/update. Keep a schema snapshot or generated extractor tests per supported JDK epoch.
Choose settings by event mechanism
| Setting/mechanism | Meaning | Failure mode |
|---|---|---|
enabled |
requested enablement; combined across active recordings | unknown/unregistered/unsupported event yields no useful data |
threshold |
requested minimum duration; effective bound can be lower during overlap | short costly events disappear; lowering can flood |
period |
periodic event cadence/convention | not every periodic event samples every entity; phase aliasing |
throttle |
implementation-defined event rate or interval control | drops/subsampling require weight/lost-event interpretation |
stackTrace |
capture commit stack when supported | stack walking/storage dominates high-rate events |
| custom filter/settings | event-specific selection | semantics and syntax are event/JDK-specific |
For waits, choose by Java mechanism and target-JDK event metadata: monitor enter, monitor wait,
thread park, socket/file I/O, virtual-thread pinning, and executor/application queues are not
interchangeable. A ThreadPark stack locates the parked code; it does not identify the resource
owner or full request delay without correlation.
Thresholds create left censoring. If 100,000 waits of 200 µs matter but the threshold is 10 ms, absence is expected. Lower progressively on a canary, measure event rate/overhead, or use an aggregate/custom counter when recording every event is not viable.
JFC and configuration composition
Prefer generating/reviewing configuration using the target JDK's jfr configure and checked-in
JFC files. Treat the JFC as code:
- pin its base JDK/configuration digest;
- review every enabled event and setting descriptor;
- validate XML and start a disposable JVM;
- run positive/negative workload controls;
- assert event counts, fields, thresholds, stacks, file size, and overhead;
- diff effective settings after a JDK upgrade;
- keep an emergency rollback/kill switch.
JDK distributions normally provide default and profile, but custom/provider configurations
can exist. Do not assert an exact universal list or infer semantics from labels. A continuous
recording means lifecycle/retention design, not a magic continuous name; finite-duration
constraints and start syntax are target-version inputs.
When composing multiple files/inline settings, verify precedence with effective settings and a
fixture. none, +event#setting, duplicate settings, and command-line parsing have
version-specific rules. Never rely only on a JVM start exit code; inspect JFR.check and the
resulting file.
Concurrent recordings
Multiple recordings can request different settings for the same event. JVM-side instrumentation and event production use combined active settings. HotSpot recordings share event data; do not assume a file is filtered independently by that recording's requested threshold or enabled set. A temporary high-detail recording can increase both process overhead and the event population/file size of a concurrent baseline. Filter during analysis when necessary, and record overlap windows instead of inferring capture settings from a file's configured label.
Inventory active recordings before escalation. Calibrate overlap, and stop/close by ID/name
carefully. Event isEnabled()/shouldCommit() consider active recording settings; one permissive
recording can make custom event work execute for all application calls.
Custom event decision
Use a custom event when application semantics are required to correlate JVM/resource events and ordinary metrics/traces cannot supply the relationship cheaply. Do not copy request bodies, credentials, raw URLs, arbitrary tenant IDs, trace IDs on every event, or high-cardinality objects into JFR.
Design:
- stable reverse-DNS event/annotation names and schema version;
- fields with correct
@DataAmount,@Timespan,@Timestamp,@Percentage, or custom metadata units; - bounded enums/IDs or sampled correlation tokens under privacy policy;
@StackTrace(false)by default for high-rate events;- threshold/filter/period controls appropriate to event type;
- source compatibility and reader behavior for added/removed/renamed fields;
- explicit event-rate and payload-size limits.
@Relational is a meta-annotation applied to a custom annotation type, which is then placed
on related fields. Applying @Relational directly to a field does not compile. It marks a
semantic relationship for tools; it does not automatically join events or enforce referential
integrity.
Custom event hot-path pattern
Event.isEnabled() is an instance method; MyEvent.isEnabled() is not a valid static guard.
Options:
- allocate the event and rely on JIT elimination when disabled, as the JFR API documents;
- cache/use
EventType.getEventType(MyEvent.class).isEnabled()as a coarse precheck before expensive payload construction, while still usingshouldCommit()for duration threshold; - restructure to collect expensive fields only after the event's duration qualifies.
Pattern:
RequestEvent event = new RequestEvent();
event.begin();
try {
handle(request);
} finally {
event.end();
if (event.shouldCommit()) {
event.operation = boundedOperation(request);
event.result = boundedResult(request);
event.commit();
}
}
shouldCommit() accounts for enablement and duration threshold after end(). It does not make
the operation or event allocation free, and concurrent settings can change dynamically. Use a
static EventType check only as an optimization proven safe with class registration and tests.
Do not quote nanoseconds/cycles or an event-rate threshold as universal. Benchmark disabled, enabled-without-stack, enabled-with-stack, burst, concurrent recording, and exporter-failure arms on the target JVM/workload.
The snippet is partial application instrumentation: payload helpers must be bounded and safe on success and failure, without masking the business exception. See Custom events and consumers when implementing the event schema, failure handling or consumer lifecycle.
Recording APIs
Choose intentionally:
Recording: lifecycle/settings/destination/dump control inside the process;RecordingStream: local live event consumption;EventStream.openRepository/file APIs where supported for repository/offline consumption;RecordingFile: sequential offline event access;FlightRecorderMXBean: remote management under a secured, versioned management boundary;jcmd/jfr: operational control and inspection.
RecordingStream.start() blocks until stream completion; startAsync() starts a consumer
thread. Neither solves callback backpressure. Event handlers must be fast, bounded, exception-
safe, and decoupled from network I/O. Define queue/drop/spool/shutdown behavior and monitor
consumer delay, drops, callback errors, memory, and repository retention.
Closing a stream/recording, process exit, and diskless/disk-backed behavior have distinct data survival semantics. Test dump/readability on the deployed JDK; do not call a memory-only recording a durable circular buffer without proving retention and failure behavior.
Parsing and schema evolution
Prefer the typed consumer API for robust Java processing. With CLI JSON/XML:
- inspect the output schema from the exact JDK/tool/version/options;
- parse duration/data/timestamp content types semantically, not by assumed numeric units;
- handle nested values, null stack/thread, arrays, experimental fields, and unknown fields;
- reject malformed/non-finite data and duplicate/unexpected types according to policy;
- assert expected event population and coverage, but distinguish “valid zero events” from configuration/parser failure using a positive control or manifest;
- preserve raw recording and converter command.
Specific fields such as GC pause summaries or virtual-thread reasons can change. Query metadata descriptors and use versioned adapters rather than a global table of names.
Java 25 considerations
JDK 25 delivered:
- JEP 509, experimental JFR CPU-time profiling on supported Linux systems;
- JEP 518, cooperative JFR sampling, changing execution-sampling internals without adding a user-facing “cooperative” event;
- JEP 520, method timing and tracing through selective bytecode instrumentation.
Discover event annotations/settings/platform support in the deployed build. CPU-time sample throttle, lost/quality fields, and stock enablement must be read from metadata/JFC/source for that update. Method timing/tracing cost scales with selected invocation rate, transformation, stack/threshold settings, and class behavior; use narrow filters and a canary/bounded window. Generic Java 17/21 readers can discover many new event types through recording metadata, but format compatibility, event interpretation and command options still require fixture tests. Examples use Java 17-compatible core JFR APIs unless marked JDK 25; inspect project toolchains before applying newer features, without implicitly upgrading Java or enabling experiments.
Troubleshooting
| Symptom | Distinguish | Action |
|---|---|---|
| No events | event absent/unregistered/disabled/thresholded/unsupported, no workload, parser wrong | metadata + active settings + positive control + raw summary |
| File huge/latency rises | event rate, stack trace, threshold/period, concurrent recording, chunk/export | disable highest amplification, retain diagnostics, recalibrate |
| Fine contention absent | threshold censoring or wrong wait mechanism | map code state to event; lower on canary and count rate |
| Custom event allocated but absent | duration below threshold, no active recording, schema/reader mismatch | call shouldCommit() after end; inspect metadata/settings and test event |
| RecordingStream falls behind | slow callbacks/network, unbounded queue, event burst | bounded handoff/drop policy; consumer-lag health; reduce event volume |
| Old parser breaks after JDK update | event/field/content-type/JSON schema changed | version adapter from metadata; preserve raw file |
| Continuous baseline cost jumps during incident | second recording's permissive effective settings | inventory concurrent recordings; stop/rollback targeted escalation |
Definition of done
- Target event/schema/settings were discovered from the deployed JDK and saved by epoch.
- Threshold/period/throttle/stack/filter choices trace to a minimum observable effect.
- Positive/negative controls prove event population, fields, weight, and censoring.
- CPU, allocation, tail latency, file/chunk/repository, loss, and consumer lag stay in budget.
- Concurrent recordings and dynamic custom-event enablement are tested.
- Custom fields are bounded, unit-annotated, privacy-reviewed, and schema-versioned.
- Consumer backpressure, exception, shutdown, disk full, restart, and malformed input paths are exercised.
- Raw recording, effective configuration, commands, versions, checksum, and extraction assertions survive.
References
- Event discovery and configuration — when selecting settings, calibrating coverage or validating a parser against a target JDK.
- Custom events and consumers — when defining events or implementing recording/streaming/offline consumers.
- JFR API Programmer's Guide
- JFR package API
- JFR consumer package
- JDK 25
jfrtool - JDK 25
jcmdtool