Java notes
JFR as the Default: Enabling Continuous Flight Recording Safely
Bounded, always-on recordings you can dump during an incident, and a parser that turns them into flame graph input
Why continuous recording
A safe baseline configuration
-XX:FlightRecorderOptions=repository=/var/tmp/jfr
-XX:StartFlightRecording=name=continuous,delay=2m,disk=true,maxage=30m,maxsize=250m,settings=default,dumponexit=true,filename=/var/tmp/jfr/exit.jfr
delay=2mwaits until the application has started and warmed up before recording, so startup noise does not fill your window.disk=truewrites data to disk, which allows long windows without holding everything in memory.maxage=30mandmaxsize=250mbound the recording. The data is discarded when either limit is hit, which is what keeps disk use predictable.settings=defaultselects the standard event template, which is designed to be low overhead. Use the heavierprofiletemplate only for short, targeted sessions.dumponexit=truesaves the recording when the JVM exits, which helps with crashes and deliberate restarts.
Capturing data during an incident
jcmd <pid> JFR.dump name=continuous filename=/var/tmp/jfr/incident-$(date +%s).jfr
Defaults per service class
Analyze a recording in Java
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordingFile;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.*;
public final class JfrAllocationFolder {
public static void main(String[] args) throws IOException {
Path jfr = Path.of(args[0]);
Path out = Path.of(args[1]);
Map<String, Long> bytesByStack = new HashMap<>();
for (RecordedEvent event : RecordingFile.readAllEvents(jfr)) {
if (!"jdk.ObjectAllocationSample".equals(event.getEventType().getName())) {
continue;
}
RecordedStackTrace trace = event.getStackTrace();
if (trace == null) {
continue;
}
List<RecordedFrame> frames = trace.getFrames();
List<String> names = new ArrayList<>();
for (int i = frames.size() - 1; i >= 0; i--) { // root first
var method = frames.get(i).getMethod();
names.add(method.getType().getName() + "." + method.getName());
}
String key = String.join(";", names);
bytesByStack.merge(key, event.getLong("weight"), Long::sum);
}
try (PrintWriter w = new PrintWriter(out.toFile())) {
bytesByStack.forEach((stack, bytes) -> w.println(stack + " " + bytes));
}
}
}
Gate your visualizations
Failure modes
- Oversized recordings. Always set
maxageandmaxsize. - Mismatched JDK versions. A recording from one JDK may not parse with a very different tool version. Keep your analysis tools at or above the JDK you run.
- Using the heavy template by default. Reserve
profilefor short investigations. - No access plan. Decide in advance who can dump recordings and where they are stored, because recordings can contain sensitive data such as stack traces and thread names.
Demo to build
Takeaways
- Continuous JFR is safe when it is bounded by age and size.
- Dump on demand with
jcmdand analyze with the consumer API or a flame graph. - Verify that data exists before visualizing it.