本文內容來自redhat某個人的文章
首先聲明了一個事實,docker容器的-m,kubernets的-limits都可以用來限制內存。當進程使用的內存超過限制時,會收到內核發來的KILL信號。但是JVM完全不知道自己運行在容器內。
那么就有了一個問題“JVM內存超過容器限制的內存會怎樣”。
作者做了一個實驗,實驗流程如下。使用如下命令啟動了一個jboss的wildfly服務器,內存限制是50m
docker run -it –name mywildfly -m=50m jboss/wildfly
docker state 顯示的信息如下
但是在一會后,Wildfly容器執行被中斷,然后打印出信息如下
*** JBossAS process (55) received KILL signal ***
執行 docker inspect mywildfly -f ‘{{json .State}}' 查看容器信息
注意到 OOMKilled=true
然后作者又做了另外一個樣例。使用springboot開接口生成字符串。(啟動參數加上 -XX:+PrintFlagsFinal )
docker run -it --rm --name mycontainer150 -p 8080:8080 -m 150M rafabene/java-container:openjdk
然后發生一個神奇的現象,-m=150m,但是JVM最大內存卻到了241.7MB,內存使用219.8MB。
倆個問題。
1.為什么最大內存是241.7MB
2.-m=150M,為什么允許Java內存到220MB?
根據JVM9的自動設置參數文檔,最大堆內存表示物理內存的四分之一。因為JVM感知不到容器的存在,所以會使用宿主機的信息來計算,如下查看參數信息
docker logs mycontainer150|grep -i MaxHeapSize uintx MaxHeapSize := 262144000 {product}
-m150m,為什么Java能開到220MB?根據Docker文檔,-m150m表示限制物理內存150M和150M虛擬內存。所以,倆個加起來總共是300M。這就是Java沒有收到Kill的原因。
回到剛開始的問題。“JVM內存超過容器限制的內存會怎樣”。會被Kill掉,那么如何讓JVM感知到容器內存的限制?
解決方案:
使用 fabric8/java-jboss-openjdk8-jdk 鏡像(此鏡像已經廢棄,最新的是這個),意思是這個鏡像會使用腳本計算容器的限制,並且只使用可用內存的百分之50。
注意:是百分之50,剛好是物理內存的限制,這樣JVM是最優的,沒有內存頁被交換到磁盤。
Dockerfile如下
FROM fabric8/java-jboss-openjdk8-jdk:1.4.0 ENV JAVA_APP_JAR java-container.jar ENV AB_OFF true EXPOSE 8080 ADD target/$JAVA_APP_JAR /deployments/
下面是Java9的解決方案。
如果你使用Java9,你可以顯示設置JVM參數,-XX:+UnlockExperimentalVMOptions ,-XX:+UseCGroupMemoryLimitForHeap 來讓JVM自動配置讀取到CGgroups的真實內存值。
注意:此時還是使用物理內存值的四分之一。
$ docker run -d --name mycontainer8g-jdk9 -p 8080:8080 -m 600M rafabene/java-container:openjdk-cgroup $ docker logs mycontainer8g-jdk9|grep MaxHeapSize size_t MaxHeapSize = 157286400 {product} {ergonomic}
下面是Java10的解決方案。
如果你使用Java10自動解決了這個,能自動感知到運行在容器內。那么-XX:+UnlockExperimentalVMOptions ,-XX:+UseCGroupMemoryLimitForHeap 這倆個參數就可以不要了。
如果加上這個參數,會看到如下警告
Option UseCGroupMemoryLimitForHeap was deprecated in version 10.0 and will likely be removed in a future release.
$ docker run -it --name mycontainer -p 8080:8080 -m 600M rafabene/java-container:openjdk10
原文鏈接