Das Symptom
Der Administrator gibt dem Container 4 GB Speicher und wundert sich, dass die Java-Anwendung mit OutOfMemoryError: Java heap space abbricht, obwohl der Container laut Monitoring nie mehr als gut 1 GB belegt. Er hebt das Containerlimit auf 8 GB, der Fehler bleibt. Der umgekehrte Irrtum kommt genauso vor: Wer -Xmx auf die volle Containergröße setzt, weil Java ja den ganzen Speicher haben soll, bekommt einen vom Kernel beendeten Container, weil neben dem Heap noch Metaspace, Threads und nativer Speicher Platz brauchen.
Die „Layer 8“-Wahrheit
Die Beschreibung des java-Kommandos erklärt beides. Erstens erkennt die JVM unter Linux Container-Grenzen automatisch. Zur Option -XX:-UseContainerSupport heißt es: „Linux only: The VM now provides automatic container detection support, which allows the VM to determine the amount of memory and number of processors that are available to a Java process running in docker containers. It uses this information to allocate system resources. The default for this flag is true, and container support is enabled by default.“ Das Limit wird also gelesen.
Zweitens nimmt sie sich davon nicht alles. Zu -XX:MaxRAMPercentage steht dort: „Sets the maximum amount of memory that the JVM may use for the Java heap before applying ergonomics heuristics as a percentage of the maximum amount determined as described in the -XX:MaxRAM option. The default value is 25 percent.“ Bei 4 GB Containerlimit sind das rund 1 GB Heap, und genau dort schlägt der OutOfMemoryError zu, während der Container aus Sicht des Orchestrators halb leer ist. Das Limit anzuheben ändert an der Rechnung nichts; es vergrößert nur das Viertel.
Die andere Richtung hat ihre eigene Grenze. -Xmx „Specifies the maximum size (in bytes) of the heap“, und der Heap ist nur ein Teil dessen, was der Prozess belegt. Wer ihn auf das Containerlimit setzt, lässt dem Rest keinen Platz, und dann beendet nicht die JVM die Anwendung mit einem Stacktrace, sondern der Kernel den Prozess ohne einen. Wer mehr Heap will, setzt -XX:MaxRAMPercentage auf etwa 60 bis 75 oder -Xmx explizit und lässt Luft für den Nicht-Heap-Anteil. -Xlog:os+container=trace zeigt beim Start, was die JVM vom Container erkannt hat.
Beide Irrtümer haben dieselbe Wurzel: Das Containerlimit wird für die Heap-Größe gehalten. Es ist weder das, was die JVM nimmt, noch das, was sie nehmen dürfte. Dazwischen liegt ein Prozentsatz, der in der Dokumentation steht und in kaum einem Deployment.
Wer das einmal mit -XX:+PrintFlagsFinal im Container gesehen hat, vergisst es nicht mehr: MaxHeapSize zeigt in Bytes, was die JVM sich genommen hat, und die Zahl ist ein Viertel dessen, was der Orchestrator dem Container zugesteht. Alles Weitere ist eine Entscheidung über den Prozentsatz, und die trifft man besser in der Startkonfiguration als in der Nacht, in der der Container zum dritten Mal neu startet.