Profiling and JVM Diagnosis
Monitoring finds the symptom; profiling explains where resources went; benchmarking measures a repeatable workload under controlled conditions. A profile taken under a debugger or heavy instrumentation is perturbed evidence. Keep that distinction in every report.
The practical JVM approach complements the concurrency, classloading, performance and runtime grounding of The Well-Grounded Java Developer, second edition. This page is a Reaktor operating guide based on primary tool documentation, not a transcription of the book.
Start with a hypothesis
| Symptom | First evidence | Follow-up |
|---|---|---|
| High latency and high CPU | CPU samples, GC and runnable-thread timeline | Attribute hot stacks to operation and exact source |
| High latency and low CPU | Wall-time stacks, waits/locks, I/O and pool queues | Distinguish dependency delay, contention and scheduling |
| High allocation and frequent GC | Allocation samples and GC/live-set timeline | Separate transient churn from retention |
| Increasing post-GC heap | Comparable heap/retention evidence | Dominators, paths to roots, owner lifecycle |
| RSS grows while heap is stable | Native/process accounting, buffers, threads, mappings | NMT for JVM internals; OS/native tools for uncovered allocations |
| Threads or handles grow | Repeated counts and thread/resource ownership | Repeated attach/detach, timeout, cancellation and close cases |
| Slow first request or startup | Startup/JIT/classloading timeline | Keep cold, warmed and steady-state results separate |
Use instance diagnostics for the metric semantics and load tests for representative workload construction.
JVM capture workflow
Run diagnosis against the selected process with matching access and container/PID context. Discover the target JVM's supported commands with jcmd "$JVM_PID" help; capabilities vary by version. Choose an artifact directory with sufficient space and an approved retention policy.
# Set JVM_PID to the selected application PID; PROFILE_DIR is an existing directory.
jcmd "$JVM_PID" VM.version
jcmd "$JVM_PID" VM.flags
jcmd "$JVM_PID" GC.heap_info
jcmd "$JVM_PID" Thread.print -l
jcmd "$JVM_PID" JFR.start name=reaktor-diagnosis settings=profile duration=60s filename="$PROFILE_DIR/diagnosis.jfr"
JFR default settings suit ongoing lower-overhead observation; profile collects more for a bounded investigation. Inspect the recording in JMC with request/GC/thread context. Heap dumps and class histograms have higher impact and require a separate bounded capture decision. A heap dump can request a full GC; do not put it on a routine health-check path. JDK 25 jcmd reference.
For a suspected JVM-native leak, start the qualified process with -XX:NativeMemoryTracking=summary, establish a baseline and compare after the reproducing interval:
jcmd "$JVM_PID" VM.native_memory baseline
# Run the fixed reproducing workload, then:
jcmd "$JVM_PID" VM.native_memory summary.diff
NMT must be enabled at startup. It tracks HotSpot internals, not every third-party native allocation; record its overhead and pair it with process memory evidence. Reserved address space differs from committed memory. Native Memory Tracking.
Use async-profiler for the supported CPU, wall, allocation or lock question. These modes answer different questions; allocation samples do not prove retention. Pin version, platform, permissions and settings, and retain the original output. Profiling modes.
Cross-platform qualification
Android system traces/simpleperf, Apple Instruments, browser performance/heap tools and native profilers remain the specialized capture tools. Reaktor adapters normalize the capture envelope and a small summary, rather than replacing the tools or pretending their semantics match.
For Workers, do not infer CPU time by subtracting performance.now() around a tight loop: Worker clocks advance on I/O according to the runtime's documented constraints. Use supported provider CPU observations and qualify any local profiling claim separately. Worker timers.
The October FlexBuffers review now retains JVM CPU/allocation artifacts and measured host, browser and physical-device codec results. That evidence does not qualify every capture script, an FFI hop, or the book's proposed mutable engine.
The flexbuffer profiling scripts are useful source material, not a blanket five-platform qualification. The September review recorded broken orchestration and Android/iOS capture issues. Re-run each current script on its intended target before promotion; require an actual artifact per supported profile.
Capture and storage contract
Store target/activation, exact artifact and source identities, workload/base, runtime/device, build mode, timestamps, tool/settings, symbols, acquisition method, coverage and perturbation. Keep a full original artifact plus a normalized ReaktorPerformanceReport for budgets. Perfetto is useful where supported; JFR, Instruments and .cpuprofile files require explicit supported conversion, not a renamed extension.
Symbolicate against the executed artifact, not the latest build. Missing symbols and unattributed samples remain visible. Bind an on-demand capture to graph scope, duration, byte/resource budgets, cancellation, expiry and a cleanup receipt. Large dumps and captures have narrower access than aggregate metrics.
A diagnosis is complete when
The finding identifies a mechanism with supporting evidence, a fixed workload reproduces it, the candidate improves the relevant absolute/relative measure without breaking correctness, and the repeated lifecycle releases resources. Retain uncertainty and run-to-run variation. Reaktor's graph maps evidence to ownership; it does not turn sampled stacks into proof of all executions.