Stop Burning CPU on Continuous JFR: Trigger Ephemeral Snapshots with Micrometer in JDK 26
Running continuous JFR streaming on high-density JDK 26 Virtual Thread pools wastes precious CPU cycles and floods storage with useless allocation telemetry. You don't need gigabytes of idle traces; you need an automated 5-second diagnostic snapshot precisely when tail latency spikes past your P99.9 SLA.
Shameless plug: javalld.com has full LLD implementations with step-by-step execution traces — free to use while prepping.
Why Most Developers Get This Wrong
- Leaving continuous
profileJFR streaming on in production, burning a 3-5% CPU tax on tens of thousands of uninteresting virtual thread executions. - Relying on periodic static thread dumps that completely miss microsecond-level carrier thread pinning and transient lock contention.
- Storing long-running JFR recordings locally until disk space exhausts, rather than generating targeted diagnostic artifacts tied directly to trace IDs.
The Right Way
Use Micrometer Observation Handlers as real-time circuit breakers that spawn short, dynamic JFR snapshots only during tail-latency anomalies.
- Keep a low-overhead custom
ObservationHandlerwired into your application's Micrometer registry. - Evaluate request duration on
onStop()against dynamic percentile thresholds (e.g., P99.9 > 500ms). - Spin off an asynchronous, non-blocking 5-second JDK
Recordingsession off the critical execution path. - Flush ephemeral recordings directly to object storage with contextual metadata attached.
Show Me The Code
public class JfrAnomalyHandler implements ObservationHandler<Observation.Context> {
private static final long SLA_THRESHOLD_NS = 500_000_000L; // 500ms P99.9 SLA
@Override
public void onStop(Observation.Context context) {
if (context.getDuration() != null && context.getDuration().toNanos() > SLA_THRESHOLD_NS) {
CompletableFuture.runAsync(() -> {
try (var rec = new Recording(Configuration.getConfiguration("profile"))) {
rec.start();
Thread.sleep(5000); // Capture 5s diagnostic window
rec.dump(Path.of("/tmp/jfr-p99-" + System.currentTimeMillis() + ".jfr"));
} catch (Exception e) { /* handle telemetry failure */ }
});
}
}
}
Key Takeaways
- Zero Idle Tax: Pay the profiling performance penalty only when your application is actively degrading.
- Context-Aware Diagnostics: Pinpoint exact Virtual Thread carrier starvation and pinning without sifting through gigabytes of dead telemetry.
- Micro-Snapshots Work: A 5-second dynamic burst gives you 100% of the root-cause data needed for JDK 26 runtime issues without storage bloat.
Top comments (0)