JVM Tuning and Profiling
AdvancedJVM tuning involves sizing the heap, choosing GC settings, and using profiling tools to diagnose CPU, memory, and thread bottlenecks.
Overview
JVM tuning starts with measurement — never tune blindly. Tools: jcmd (command-line diagnostics), jmap/jhat (heap dumps), jstack (thread dumps), JConsole/VisualVM (GUI monitoring), Java Flight Recorder (JFR) + Mission Control (production-safe profiling). Key tuning areas: heap sizing, GC algorithm selection, JIT thresholds, and thread pool sizes.
Heap Sizing and GC Flags
Setting -Xms equal to -Xmx avoids heap resizing pauses. Leave headroom above live data: GC needs 2–3× the live set to work efficiently.
For G1GC, the key knobs are MaxGCPauseMillis (pause target) and InitiatingHeapOccupancyPercent (when to start concurrent marking). For ZGC, ensure sufficient heap headroom (2× live set minimum).
# Production JVM flags template
# Heap sizing
-Xms4g -Xmx4g # fixed heap — avoids resizing pauses
-XX:+UseG1GC # G1GC (default Java 9+)
-XX:MaxGCPauseMillis=100 # target max pause
# GC logging (Java 9+ unified logging)
-Xlog:gc*:file=/logs/gc.log:time,level,tags:filecount=5,filesize=20m
# Diagnostics
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/dumps/heap-$(date +%s).hprof
-XX:+ExitOnOutOfMemoryError # restart rather than limp on
# JFR continuous profiling (low overhead ~1%)
-XX:StartFlightRecording=filename=recording.jfr,settings=profile,duration=60s
# Metaspace
-XX:MetaspaceSize=256m # initial Metaspace commit size
-XX:MaxMetaspaceSize=512m # cap to prevent unbounded growthDiagnosing Problems with jcmd and JFR
jcmd is the Swiss-army knife for live JVM diagnostics — trigger GC, take heap/thread dumps, start JFR recordings, all without restarting the JVM.
Java Flight Recorder (JFR) is a production-safe continuous profiler built into the JDK. Overhead is ~1-2%. It records CPU, allocations, GC, lock contention, I/O, and more. Analyse with JDK Mission Control (JMC).
# List running JVMs
jcmd
# Heap summary
jcmd <pid> GC.heap_info
# Trigger GC
jcmd <pid> GC.run
# Thread dump (better than jstack for modern JVMs)
jcmd <pid> Thread.print
# Heap dump (for memory leak analysis in VisualVM/Eclipse MAT)
jcmd <pid> GC.heap_dump /tmp/heap.hprof
# Start JFR recording on live process
jcmd <pid> JFR.start name=myrecording settings=profile
# Dump JFR data
jcmd <pid> JFR.dump name=myrecording filename=/tmp/app.jfr
# Stop recording
jcmd <pid> JFR.stop name=myrecording
# VM flags currently in use
jcmd <pid> VM.flags
# System properties
jcmd <pid> VM.system_propertiesCommon Performance Patterns
Allocation pressure: many short-lived objects cause frequent minor GCs. Fix: pool objects, use primitives, reuse buffers. Lock contention: threads spinning on locks. Fix: reduce lock scope, use concurrent collections, CAS operations.
String deduplication: G1GC can deduplicate identical String objects (-XX:+UseStringDeduplication). StringBuilder vs + in loops. Avoid boxing/unboxing in hot paths — use primitive collections (Eclipse Collections, Trove).
// ALLOCATION PRESSURE — common antipatterns
// Bad: new object per call
String process(String input) {
return new StringBuilder(input).reverse().toString();
// StringBuilder allocated and GC'd every call
}
// Better: reuse where possible (thread-local pool)
private static final ThreadLocal<StringBuilder> SB =
ThreadLocal.withInitial(() -> new StringBuilder(256));
String process(String input) {
StringBuilder sb = SB.get();
sb.setLength(0); // reset, don't allocate
return sb.append(input).reverse().toString();
}
// BOXING PRESSURE — avoid in tight loops
// Bad
Map<Integer, Long> map = new HashMap<>();
for (int i = 0; i < 1_000_000; i++) {
map.put(i, map.getOrDefault(i, 0L) + 1); // boxes int, Long every iteration
}
// Better — use primitive maps (Eclipse Collections)
MutableIntLongMap primMap = IntLongMaps.mutable.empty();
primMap.addToValue(i, 1L); // zero boxingKey Points to Remember
- Set -Xms equal to -Xmx to avoid heap resize pauses in production.
- Always enable -XX:+HeapDumpOnOutOfMemoryError and -XX:+ExitOnOutOfMemoryError.
- jcmd is the preferred tool for live diagnostics — heap dumps, thread dumps, JFR control.
- JFR has ~1% overhead — safe for continuous production profiling.
- Allocation pressure and lock contention are the two most common Java performance bottlenecks.
Practice JVM Tuning and Profiling in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat JVM flags would you set for a production Java service?
How do you take a heap dump and what tool do you use to analyse it?
What is Java Flight Recorder and why is it preferred for production profiling?
What is allocation pressure and how does it affect GC?
How would you diagnose high CPU usage in a Java application?
Ask Aria about JVM Tuning and Profiling
Your personal AI tutor — ask anything about this concept