Add flags: production JVM flag cheat sheet for Spring Boot on JDK 25/27

MaxRAMPercentage vs a hardcoded -Xmx, -XX:-UseContainerSupport, the
ActiveProcessorCount lie, and which diagnostic flags are worth leaving
on in prod, verified in real Docker containers on JDK 25 and JDK 27.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-10-01 04:34:04 +00:00
co-authored by Claude Sonnet 5
parent e83c9949d8
commit 2f88734f5d
22 changed files with 832 additions and 0 deletions
@@ -0,0 +1,83 @@
package com.ankurm.flagsdemo;
import com.sun.management.HotSpotDiagnosticMXBean;
import com.sun.management.OperatingSystemMXBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Prints what the JVM actually resolved its container-relevant flags to, at
* runtime, inside whatever cgroup it is running under. This is the live
* version of what docs/output/*.txt captures from -XX:+PrintFlagsFinal; the
* two should agree, and the post at ankurm.com cross-checks both.
*
* <p>Explained in the post:
* https://ankurm.com/jvm-flags-for-spring-boot-in-containers-jdk-25-27/
*
* <p><strong>Delete this endpoint before shipping to a real service.</strong>
* It leaks heap sizing and cgroup limits to anyone who can reach it.
*/
@RestController
public class JvmDiagnosticsController {
private static final List<String> FLAGS_OF_INTEREST = List.of(
"MaxRAMPercentage", "MinRAMPercentage", "InitialRAMPercentage",
"MaxRAM", "MaxHeapSize", "InitialHeapSize", "MinHeapSize",
"UseContainerSupport", "ActiveProcessorCount",
"UseG1GC", "UseParallelGC", "UseZGC", "UseSerialGC",
"UseCompactObjectHeaders", "ParallelGCThreads", "ConcGCThreads"
);
@GetMapping("/internal/jvm-flags")
public Map<String, Object> jvmFlags() {
Map<String, Object> out = new LinkedHashMap<>();
OperatingSystemMXBean os =
(OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
Map<String, Object> hostView = new LinkedHashMap<>();
hostView.put("availableProcessors", Runtime.getRuntime().availableProcessors());
hostView.put("totalMemorySizeBytes", os.getTotalMemorySize());
hostView.put("freeMemorySizeBytes", os.getFreeMemorySize());
out.put("osMXBeanView", hostView);
MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = mem.getHeapMemoryUsage();
Map<String, Object> heapView = new LinkedHashMap<>();
heapView.put("initBytes", heap.getInit());
heapView.put("usedBytes", heap.getUsed());
heapView.put("committedBytes", heap.getCommitted());
heapView.put("maxBytes", heap.getMax());
out.put("heap", heapView);
List<String> gcNames = ManagementFactory.getGarbageCollectorMXBeans()
.stream().map(GarbageCollectorMXBean::getName).toList();
out.put("garbageCollectors", gcNames);
HotSpotDiagnosticMXBean diag =
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
Map<String, String> flagView = new LinkedHashMap<>();
for (String name : FLAGS_OF_INTEREST) {
try {
flagView.put(name, diag.getVMOption(name).getValue());
} catch (IllegalArgumentException notThisJvm) {
flagView.put(name, "N/A on this build");
}
}
out.put("resolvedFlags", flagView);
out.put("jvmInputArguments",
ManagementFactory.getRuntimeMXBean().getInputArguments());
out.put("javaVersion", System.getProperty("java.version"));
out.put("javaVmName", System.getProperty("java.vm.name"));
return out;
}
}