Skip to content

JVM Performance Tuning Guide

JVM tuning is not magic — it is a loop: measure → change one thing → verify. This guide gives you the workflow, the symptoms-to-fixes map, and the knobs that matter.

Step 1: Measure before you touch anything

Never tune blind. Enable GC logging (safe in production, < 3% overhead) and analyze a window that covers peak traffic:

bash
# Java 9+
java -Xlog:gc*:file=gc.log:time,uptime -jar app.jar
# Java 8
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar

Analyze the log with EasyGC →

The report tells you which of the four common problems you actually have.

Step 2: Match the symptom to the fix

Symptom in the reportLikely causeWhat to try
Frequent Full GCold gen too small / leak / oversized cacheRaise -Xmx; check for leaks (after-GC heap climbing); review cache sizes
High allocation rateshort-lived object churnObject pooling or caching hot paths; reduce per-request garbage
Max pause > SLOwrong collector or heap too big for collectorSwitch to G1/ZGC; for G1 set -XX:MaxGCPauseMillis
Throughput < 90%GC frequency too highBigger young gen (-Xmn / -XX:NewRatio); bigger heap
Metaspace GCsclassloader churnRaise -XX:MaxMetaspaceSize; hunt dynamic proxies
System.gc() in causesexplicit calls (RMI/NIO)-XX:+DisableExplicitGC — verify Direct ByteBuffer reliance first
Humongous allocations (G1)objects > half a regionRaise -XX:G1HeapRegionSize; refactor large arrays

Step 3: Change one knob at a time

Tuning one thing, re-measuring, and comparing reports is the only way to know what worked. The knobs that cover 90% of cases:

KnobEffect
-Xms = -XmxFixed heap size — removes resize pauses, more predictable
-Xmn / -XX:NewRatioYoung gen size — the main lever on GC frequency
-XX:+UseG1GC / -XX:+UseZGCCollector choice — the main lever on pause length
-XX:MaxGCPauseMillis=200G1 pause target (don't set it unrealistically low)
-XX:MaxMetaspaceSizeClass metadata cap
-XX:+DisableExplicitGCIgnore System.gc() calls

Step 4: Verify and set a baseline

Re-analyze the new GC log and compare it with the old report: throughput up? max pause down? Full GCs gone? Save both reports (PDF/JSON) as your baseline — after the next release, one analysis tells you whether GC behavior regressed.

Checklist

TIP

New to the terminology? Start with JVM vs JRE vs JDK, then come back.