在我們日常的工作當中,通常應用都會採用Kubernetes進行容器化部署,但是總是會出現一些問題,例如,JVM堆小於Docker容器中設定的記憶體大小和Kubernetes的記憶體大小,但是還是會被OOMKilled。在此我們介紹一下K8s的OOMKilled的Exit Code編碼。
因為我的heap大小肯定是小於Docker容器以及Pod的大小的,為啥還是會出現OOMKilled?
這種問題常發生在JDK8u131或者JDK9版本之後所出現在容器中執行JVM的問題:在大多數情況下,JVM將一般預設會採用宿主機Node節點的記憶體為Native VM空間(其中包含了堆空間、直接記憶體空間以及棧空間),而並非是是容器的空間為標準。
docker run -m 100MB openjdk:8u121 java -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 444.50M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
以上的資訊出現了矛盾,我們在執行的時候將容器記憶體設定為100MB,而-XshowSettings:vm列印出的JVM將最大堆大小為444M,如果按照這個記憶體進行分配記憶體的話很可能會導致節點主機在某個時候殺死我的JVM。
一種方法解決 JVM 記憶體超限的問題,這種方法可以讓JVM自動感知 docker 容器的 cgroup 限制,從而動態的調整堆記憶體大小。JDK8u131在JDK9中有一個很好的特性,即JVM能夠檢測在Docker容器中執行時有多少記憶體可用。為了使jvm保留根據容器規範的記憶體,必須設定標誌-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap。
注意:如果將這兩個標誌與Xms和Xmx標誌一起設定,那麼jvm的行為將是什麼?-Xmx標誌將覆蓋-XX:+ UseCGroupMemoryLimitForHeap標誌。
標誌-XX:+ UseCGroupMemoryLimitForHeap使JVM可以檢測容器中的最大堆大小。
-Xmx標誌將最大堆大小設定為固定大小。
除了JVM的堆空間,還會對於非堆和jvm的東西,還會有一些額外的記憶體使用情況。
$ docker run -m 100MB openjdk:8u131 java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 44.50M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
可以看出來通過記憶體感知之後,JVM能夠檢測到容器只有100MB,並將最大堆設定為44M。我們調整一下記憶體大小看看是否可以實現動態化調整和感知記憶體分配,如下所示。
docker run -m 1GB openjdk:8u131 java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 228.00M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
我們設定了容器有1GB記憶體分配,而JVM使用228M作為最大堆。因為容器中除了JVM之外沒有其他程序在執行,所以我們還可以進一步擴大一下對於Heap堆的分配?
$ docker run -m 1GB openjdk:8u131 java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=1 -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 910.50M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
在較低的版本的時候可以使用-XX:MaxRAMFraction引數,它告訴JVM使用可用記憶體/MaxRAMFract作為最大堆。使用-XX:MaxRAMFraction=1,我們將幾乎所有可用記憶體用作最大堆。從上面的結果可以看出來記憶體分配已經可以達到了910.50M。
但如果容器使用堆外記憶體,這可能會有風險,因為幾乎所有的容器記憶體都分配給了堆。您必須將-XX:MaxRAMFraction=2設定為堆只使用50%的容器記憶體,或者使用Xmx。
Docker1.7開始將容器cgroup資訊掛載到容器中,所以應用可以從 /sys/fs/cgroup/memory/memory.limit_in_bytes 等檔案獲取記憶體、 CPU等設定,在容器的應用啟動命令中根據Cgroup設定正確的資源設定 -Xmx, -XX:ParallelGCThreads等引數
Java10+廢除了-XX:MaxRAM引數,因為JVM將正確檢測該值。在Java10中,改進了容器整合。無需新增額外的標誌,JVM將使用1/4的容器記憶體用於堆。
java10+確實正確地識別了記憶體的docker限制,但您可以使用新的標誌MaxRAMPercentage(例如:-XX:MaxRAMPercentage=75)而不是舊的MaxRAMFraction,以便更精確地調整堆的大小,而不是其餘的(堆疊、本機…)
java10+上的UseContainerSupport選項,而且是預設啟用的,不用設定。同時 UseCGroupMemoryLimitForHeap 這個就棄用了,不建議繼續使用,同時還可以通過 -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage 這些引數更加細膩的控制 JVM 使用的記憶體比率。
Java 程式在執行時會呼叫外部程序、申請 Native Memory 等,所以即使是在容器中執行 Java 程式,也得預留一些記憶體給系統的。所以 -XX:MaxRAMPercentage 不能設定得太大。當然仍然可以使用-XX:MaxRAMFraction=1選項來壓縮容器中的所有記憶體。
本文來自部落格園,作者:洛神灬殤,轉載請註明原文連結:https://www.cnblogs.com/liboware/p/16936982.html,任何足夠先進的科技,都與魔法無異。