Performance & Optimization

Profiling

Profilers tell you where time and memory actually go. Prefer sampling profilers with low overhead — JDK Flight Recorder (built-in, production-safe) and async-profiler (flame graphs, no safepoint bias) — and profile allocation as often as CPU.
  • JFR: -XX:StartFlightRecording or jcmd JFR.start — ~1% overhead, safe in production
  • async-profiler: CPU/alloc/lock flame graphs without safepoint bias
  • Flame graph reading: width = time share; look for wide plateaus you own
  • Execution profilers hide I/O waits — wall-clock mode or JFR events catch blocked time
  • Heap analysis: jcmd GC.heap_dump + Eclipse MAT (dominator tree finds leaks)
  • Old instrumenting profilers distort hot code — trust sampling
The production toolkit
# Flight Recorder: record 2 minutes, dump to file
$ jcmd <pid> JFR.start duration=120s filename=rec.jfr settings=profile
$ jfr print --events jdk.CPULoad,jdk.GarbageCollection rec.jfr   # or open in JDK Mission Control

# async-profiler: 30 s CPU flame graph
$ asprof -d 30 -f flame.html <pid>
$ asprof -e alloc -d 30 -f alloc.html <pid>     # allocation sites instead

# Emergency triage
$ jcmd <pid> Thread.print          # what is everyone doing right now?
$ jcmd <pid> GC.heap_dump dump.hprof

Safepoint bias (Optimizing Java ch. 13): classic samplers (JVisualVM et al.) can only sample at safepoints, so they systematically attribute time to safepoint-friendly code and miss tight JIT-compiled loops. async-profiler uses AsyncGetCallTrace + perf events to sample anywhere, which is why its flame graphs are the community standard. JFR sidesteps the issue with its own event machinery and adds the whole JVM's telemetry: GC, JIT, locks, TLAB allocations, socket I/O.

Memory-leak workflow: watch old-gen occupancy after full collections trend upward (Gc Tuning Logging); take a heap dump near fullness; open the dominator tree in MAT — it ranks objects by retained size, and the leak is usually a top-3 entry (a static map, an unbounded cache, a listener list that only grows). jmap -histo:live <pid> gives a quick class histogram without the dump ceremony.

Sources
  • Optimizing JavaCh. 13 — Profiling
  • Optimizing Cloud Native Java (2nd ed.)Ch. 12 — Profiling
  • Java Secrets: High Performance and ScalabilityPerformance analysis chapters