Okay, mal ehrlich: Ich hab mich bei dem Thema JVM-Memory in Kubernetes schon öfter gefragt, warum ein Container OOMKilled wird, obwohl Java dir stolz erzählt, dass der Heap gesund ist. Und eine Meldung von learnkube.com hat mir da gerade ein bisschen die Augen geöffnet – und ich bin ehrlich: einiges davon wusste ich nicht.
Der Punkt ist eigentlich simpel, aber leicht übersehen: Ein JVM-Container hat zwei verschiedene Speichergrenzen. Da ist die Heap-Grenze, die du mit -Xmx setzt – und da ist die Container-Grenze, die über limits.memory in Kubernetes läuft. Die beiden sind nicht dasselbe, auch wenn man das gerne vergisst.
Was mich richtig zum Grinsen gebracht hat: Wenn du -Xmx auf exakt den Wert von limits.memory stellst, dann bleibt für alles, was außerhalb des Heaps liegt, gar kein Platz mehr. Und der Heap teilt sich die Container-Grenze nämlich mit einigen ziemlich neugierigen Nachbarn: Metaspace, Code-Cache, Thread-Stacks, Direct Buffers, Garbage-Collector-Strukturen und native Libraries. Die wollen auch alle was davon. Wenn der Heap sich das ganze Speicherbudget schnappt, kann der Kernel den Container killen – während Java weiterhin fröhlich meldet, dass der Heap doch völlig in Ordnung ist. Genau diese Diskrepanz, die einem das Debuggen so versaut.
Und um das konkret zu machen: Ein 4-GiB-Heap braucht einen Container, der größer als 4 GiB ist. Beim Rechnen hilft das Beispiel aus der Quelle: Da steht -XX:MaxRAMPercentage=75.0 in den JAVA_TOOL_OPTIONS, dazu -XX:ActiveProcessorCount=1 und -XX:+UseSerialGC – bei einem Request von 500m CPU und 1024Mi Memory. Also nicht alles auf 100 %, sondern ein bisschen Luft lassen.
Und CPU ist ähnlich heikel, nur in eine andere Richtung verdreht. Kubernetes entscheidet über Scheduling und CPU-Zeit, während HotSpot aus der verfügbaren CPU eine Prozessoranzahl macht – und die beeinflusst wiederum Thread-Pools und die Garbage-Collector-Standardwerte. Heißt im Klartext: Wenn die CPU falsch angegeben ist, verhält sich deine JVM nicht so, wie du denkst.
Ich geb ehrlich zu, die Sache ist messbar komplexer, als eine Tabelle auf den ersten Blick suggeriert. Der Beitrag bei learnkube.com ist wohl der erste Teil eines längeren Guides zu Kubernetes Rightsizing – und genau da liegt laut Meldung der Trick: Container-Memory vom eigentlichen App-Memory trennen und die Metriken alle zusammen auswerten. Ich bin gespannt, was die restlichen Kapitel noch verraten.
1 Kommentar zu „JVM in Kubernetes: Warum dein Heap manchmal gar nicht das Problem ist“