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]>
84 lines
3.5 KiB
Java
84 lines
3.5 KiB
Java
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;
|
|
}
|
|
}
|