JVM Ergonomics: How the JVM Auto-Tunes Itself
You’ve probably seen -Xmx flags scattered across deployment scripts, CI configs, and Dockerfiles. The default assumption is that you need to hand-tune every important JVM setting — otherwise your app will either OOM or waste memory.
This is mostly wrong. Modern HotSpot JVMs have an “ergonomics” engine that reads your host’s hardware (RAM, CPU cores) at startup and calculates sensible defaults for heap sizing, garbage collector selection, thread counts, code cache size, and more. You can skip almost all -X flags unless you have a very specific requirement.
This post walks through exactly what the JVM chose by default on this machine — no manual tuning, just ManagementFactory probes to read back the decisions.
The host the JVM sees
Ergonomics is fundamentally about translating hardware observations into VM behavior. Before it picks a single flag value, the JVM queries two things:
- Physical RAM — how much memory is available for heap allocation (read via
com.sun.management.OperatingSystemMXBean.getTotalPhysicalMemorySize()) - Available CPUs — both total and usable (the JVM may see fewer cores than exist due to cgroup constraints or other isolation)
These are the inputs. Everything else downstream is a derived value.
The code
The program below uses only java.lang.management APIs — no reflection, no native calls — to read back every ergonomic decision the HotSpot VM made at startup:
import java.lang.management.CompilationMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.List;
public class ErgonomicsDemo {
public static void main(String[] args) throws Exception {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
Runtime runtime = Runtime.getRuntime();
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
List<MemoryPoolMXBean> poolBeans = ManagementFactory.getMemoryPoolMXBeans();
long physicalMem = 0;
if (osBean instanceof com.sun.management.OperatingSystemMXBean sunOs) {
physicalMem = sunOs.getTotalPhysicalMemorySize();
}
int availableProcessors = runtime.availableProcessors();
System.out.printf(" Physical RAM: %d MB%n", physicalMem / (1024 * 1024));
System.out.printf(" Available CPUs: %d%n", availableProcessors);
long heapInit = memoryBean.getHeapMemoryUsage().getInit();
long heapMax = memoryBean.getHeapMemoryUsage().getMax();
System.out.printf(" Initial heap: %d MB (%.1f%% of RAM)%n",
toMB(heapInit), toPercent(heapInit, physicalMem));
System.out.printf(" Max heap: %d MB (%.1f%% of RAM)%n",
toMB(heapMax), toPercent(heapMax, physicalMem));
for (GarbageCollectorMXBean gc : gcBeans)
System.out.printf(" GC: %s (collections=%d)\n", gc.getName(), gc.getCollectionCount());
for (MemoryPoolMXBean pool : poolBeans)
System.out.printf(" Pool %-25s init=%6d MB max=%s%n",
pool.getName(), toMB(pool.getUsage().getInit()),
pool.getUsage().getMax() == -1 ? "unlimited" : toMB(pool.getUsage().getMax()) + "");
CompilationMXBean comp = ManagementFactory.getCompilationMXBean();
System.out.printf(" JIT type: %s\n", comp.getName());
}
private static int toMB(long bytes) { return (int) (bytes / (1024 * 1024)); }
private static double toPercent(long part, long total) {
return total > 0 ? part * 100.0 / total : 0;
}
}
It prints back the heap sizes as percentages of physical RAM, lists every memory pool that was auto-created by the GC, identifies the default collector type, and shows the JIT compiler.
Running it
Compiled and run with no explicit -Xmx, -Xms, or -XX:+Use... flags — just java -XX:+PrintCommandLineFlags ErgonomicsDemo: the only extra flag is one that prints back what ergonomics already decided.
Here are the key takeaways from the output:
The 1/4 rule, confirmed. The max heap landed at 30,688 MB, which is 24.6% of the host’s 124,607 MB (≈125 GB) physical RAM. This isn’t arbitrary — it comes from the JVM heuristic that reserves roughly three-quarters of physical memory for everything else: OS buffers, thread stacks, native libraries, Metaspace (class metadata), and direct ByteBuffers. With no explicit -Xmx, a Java process will not silently consume your entire machine.
Initial heap is tiny — it grows lazily. The starting heap was only 1,952 MB (~1.6% of RAM). This is deliberate: allocating 30 GB at startup would spike memory pressure during launch and delay the first request. Instead, the JVM starts small and expands as needed up to the computed max.
G1GC auto-selected. On JDK 21 (confirmed via -XX:+UseG1GC in the PrintCommandLineFlags line), G1 is the default collector for server-class machines. The memory pool output confirms three G1-specific pools were created automatically: G1 Eden Space, G1 Survivor Space, and G1 Old Gen — each independently managed with their own init/max sizing.
Non-heap memory is separately sized. Metaspace (class metadata) starts at 0 MB and grows dynamically without an explicit upper bound, while the CodeCache (JIT compiled code) was auto-sized to ~5–117 MB depending on compilation mode. Non-heap init was only 7 MB.
Post-warmup heap shows actual working set. After class loading, JIT compilation, and a brief allocation-and-drop cycle, the heap used 481 MB out of the available 30,688 MB max — proving that ergonomics sized the ceiling appropriately for a JVM that didn’t need anywhere near its maximum.
Takeaway
You don’t need to memorize -Xmx values or GC selection flags for most deployments. The HotSpot ergonomics engine has already done this work for you, calculating based on real host metrics at startup — not a generic default that ignores whether your machine has 2 GB or 128 GB of RAM.
Set explicit flags when you need to: when a specific GC algorithm matches your latency profile (G1 for low pause times, ZGC/Shenandoah for sub-millisecond pauses), when deployment constraints require memory guarantees outside what the heuristic provides, or when monitoring shows that ergonomics chose poorly for your actual workload shape.