容器中的JVM資源該如何被安全的限制?

前言 java

Java與Docker的結合,雖然更好的解決了application的封裝問題。但也存在着不兼容,好比Java並不能自動的發現Docker設置的內存限制,CPU限制。docker

這將致使JVM不能穩定服務業務!容器會殺死你JVM進程,而健康檢查又將拉起你的JVM進程,進而致使你監控你的pod一天重啓次數甚至能達到幾百次。緩存

咱們但願當Java進程運行在容器中時,java可以自動識別到容器限制,獲取到正確的內存和CPU信息,而不用每次都須要在kubernetes的yaml描述文件中顯示的配置完容器,還須要配置JVM參數。安全

使用JVM MaxRAM參數或者解鎖實驗特性的JVM參數,升級JDK到10+,咱們能夠解決這個問題(也許吧~.~)。app

首先Docker容器本質是是宿主機上的一個進程,它與宿主機共享一個/proc目錄,也就是說咱們在容器內看到的/proc/meminfo,/proc/cpuinfo 與直接在宿主機上看到的一致,以下。jvm

Host測試

cat /proc/meminfo
    
MemTotal: 197869260 kB
    
MemFree: 3698100 kB
    
MemAvailable: 62230260 kB

容器ui

docker run -it --rm alpine cat /proc/meminfo
    
MemTotal: 197869260 kB
    
MemFree: 3677800 kB
    
MemAvailable: 62210088 kB

那麼Java是如何獲取到Host的內存信息的呢?沒錯就是經過/proc/meminfo來獲取到的。線程

默認狀況下,JVM的Max Heap Size是系統內存的1/4,假如咱們系統是8G,那麼JVM將的默認Heap≈2G。code

Docker經過CGroups完成的是對內存的限制,而/proc目錄是已只讀形式掛載到容器中的,因爲默認狀況下Java 壓根就看不見CGroups的限制的內存大小,而默認使用/proc/meminfo中的信息做爲內存信息進行啓動, 這種不兼容狀況會致使,若是容器分配的內存小於JVM的內存,JVM進程會被理解殺死。

內存限制不兼容

咱們首先來看一組測試,這裏咱們採用一臺內存爲188G的物理機。

#free -g   
total        used        free      shared  buff/cache   available  
Mem:            188  122  1  0 64 64

如下的測試中,咱們將包含openjdk的hotspot虛擬機,IBM的openj9虛擬機。

如下測試中,咱們把正確識別到限制的jdk,稱之爲安全(即不會超出容器限制不會被kill),反之稱之爲危險。

測試用例1(OPENJDK)

這一組測試咱們使用最新的openjdk8-12,給容器限制內存爲4G,看JDK默認參數下的最大堆爲多少?看看咱們默認參數下多少版本的JDK是安全的

命令以下,若是你也想試試看,能夠用一下命令。

docker run -m 4GB --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version
    
docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version
    
docker run -m 4GB --rm  openjdk:10-jre-slim java -XshowSettings:vm  -version
    
docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version
    
docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version

OpenJDK8(並無識別容器限制,26.67G) 危險

[root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version    

VM settings:  
  Max. Heap  Size (Estimated): 26.67G
  Ergonomics  Machine  Class: server
  Using VM: OpenJDK  64-Bit  Server VM

openjdk version "1.8.0_181"   
OpenJDK  Runtime  Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13) 
OpenJDK  64-Bit  Server VM (build 25.181-b13, mixed mode)

OpenJDK8 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap (正確的識別容器限制,910.50M)安全

[root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version
    
VM settings:
Max. Heap  Size (Estimated): 910.50M
Ergonomics  Machine  Class: server
Using VM: OpenJDK  64-Bit  Server VM`
    
openjdk version "1.8.0_181" 
OpenJDK  Runtime  Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
OpenJDK  64-Bit  Server VM (build 25.181-b13, mixed mode)

OpenJDK 9(並無識別容器限制,26.67G)危險

[root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version
    
VM settings:
   Max. Heap  Size (Estimated): 29.97G
   Using VM: OpenJDK  64-Bit  Server VM  
    
openjdk version "9.0.4"
OpenJDK  Runtime  Environment (build 9.0.4+12-Debian-4)
OpenJDK  64-Bit  Server VM (build 9.0.4+12-Debian-4, mixed mode)

OpenJDK 9 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap (正確的識別容器限制,1G)安全

[root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version
    
VM settings:
   Max. Heap  Size (Estimated): 1.00G
   Using VM: OpenJDK  64-Bit  Server VM     
    
openjdk version "9.0.4"
OpenJDK  Runtime  Environment (build 9.0.4+12-Debian-4)  
OpenJDK  64-Bit  Server VM (build 9.0.4+12-Debian-4, mixed mode)

OpenJDK 10(正確的識別容器限制,1G)安全

[root@xiaoke-test ~]# docker run -m 32GB --rm  openjdk:10-jre-slim java -XshowSettings:vm -XX:MaxRAMFraction=1 -version
    
VM settings: 
Max. Heap  Size (Estimated): 1.00G  
Using VM: OpenJDK  64-Bit  Server VM
    
openjdk version "10.0.2"  2018-07-17
    
OpenJDK  Runtime  Environment (build 10.0.2+13-Debian-2)
OpenJDK  64-Bit  Server VM (build 10.0.2+13-Debian-2, mixed mode)

OpenJDK 11(正確的識別容器限制,1G)安全

[root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version
    
VM settings:
Max. Heap  Size (Estimated): 1.00G
Using VM: OpenJDK  64-Bit  Server VM
    
openjdk version "11.0.1"  2018-10-16    
OpenJDK  Runtime  Environment (build 11.0.1+13-Debian-3)
OpenJDK  64-Bit  Server VM (build 11.0.1+13-Debian-3, mixed mode, sharing)

OpenJDK 12(正確的識別容器限制,1G)安全

[root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version
    
VM settings:
    Max. Heap  Size (Estimated): 1.00G
    Using VM: OpenJDK  64-Bit  Server VM
    
openjdk version "12-ea"  2019-03-19  
OpenJDK  Runtime  Environment (build 12-ea+23)
OpenJDK  64-Bit  Server VM (build 12-ea+23, mixed mode, sharing)
測試用例2(IBMOPENJ9)

docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version
    
docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version
    
docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version

docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java -XshowSettings:vm  -version

openjdk8-openj9 (正確的識別容器限制,3G)安全

[root@xiaoke-test ~]# docker run -m 
4GB
 --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version

VM settings:    
Max. Heap Size (Estimated): 3.00G
Ergonomics Machine Class: server
Using VM: Eclipse OpenJ9 VM

openjdk version "1.8.0_192"
OpenJDK Runtime Environment (build 1.8.0_192-b12_openj9)
Eclipse OpenJ9 VM (build openj9-0.11.0, JRE 1.8.0Linuxamd64-64-Bit Compressed References 20181107_95(JIT enabled, AOT enabled)

OpenJ9 - 090ff9dcd
OMR    - ea548a66
JCL    - b5a3affe73 based on jdk8u192-b12)

openjdk9-openj9 (正確的識別容器限制,3G)安全

[root@xiaoke-test ~]#docker run -m 4GB --rm adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version
    
VM settings:  
Max. Heap  Size (Estimated): 3.00G  
Using VM: Eclipse  OpenJ9 VM    
    
openjdk version "9.0.4-adoptopenjdk"
OpenJDK  Runtime  Environment (build 9.0.4-adoptopenjdk+12)
Eclipse  OpenJ9 VM (build openj9-0.9.0, JRE 9  Linux amd64-64-Bit  Compressed  References  20180814_248 (JIT enabled, AOT enabled)
    
OpenJ9 - 24e53631   
OMR   - fad6bf6e    
JCL   - feec4d2ae based on jdk-9.0.4+12)

openjdk10-openj9 (正確的識別容器限制,3G)安全

[root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version
    
VM settings:
Max. Heap  Size (Estimated): 3.00G
Using VM: Eclipse  OpenJ9 VM
   
    
openjdk version "10.0.2-adoptopenjdk"  2018-07-17 
OpenJDK  Runtime  Environment (build 10.0.2-adoptopenjdk+13)
Eclipse  OpenJ9 VM (build openj9-0.9.0, JRE 10  Linux amd64-64-Bit  Compressed  References  20180813_102 (JIT enabled, AOT enabled)
    
OpenJ9 - 24e53631   
OMR    - fad6bf6e
JCL    - 7db90eda56 based on jdk-10.0.2+13)

openjdk11-openj9(正確的識別容器限制,3G)安全

[root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java -XshowSettings:vm  -version
    
VM settings:   
Max. Heap  Size (Estimated): 3.00G
Using VM: Eclipse  OpenJ9 VM
    
openjdk version "11.0.1"  2018-10-16 
OpenJDK  Runtime  Environment  AdoptOpenJDK (build 11.0.1+13)   
Eclipse  OpenJ9 VM AdoptOpenJDK (build openj9-0.11.0, JRE 11  Linux amd64-64-Bit  Compressed  References  20181020_70 (JIT enabled, AOT enabled)
    
OpenJ9 - 090ff9dc   
OMR   - ea548a66    
JCL   - f62696f378 based on jdk-11.0.1+13)
分析

分析以前咱們先了解這麼一個狀況:

JavaMemory (MaxRAM) = 元數據+線程+代碼緩存+OffHeap+Heap...

通常咱們都只配置Heap即便用-Xmx來指定JVM可以使用的最大堆。而JVM默認會使用它獲取到的最大內存的1/4做爲堆的緣由也是如此。

安全性(即不會超過容器限制被容器kill)

OpenJdk

OpenJdk8-12,都能保證這個安全性的特色(8和9須要特殊參數,-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap)。

OpenJ9

2.IbmOpenJ9全部的版本都能識別到容器限制。

資源利用率

OpenJdk

自動識別到容器限制後,OpenJdk把最大堆設置爲了大概容器內存的1/4,對內存的浪費不可謂不大。

固然能夠配合另外一個JVM參數來配置最大堆。-XX:MaxRAMFraction=int。下面是我整理的一個常見內存設置的表格, 從中咱們能夠看到彷佛JVM默認的最大堆的取值爲MaxRAMFraction=4,隨着內存的增長,堆的閒置空間愈來愈大,在16G容器內存時,java堆只有不到4G。

MaxRAMFraction取值    堆佔比 容器內存=1G 容器內存=2G 容器內存=4G 容器內存=8G 容器內存=16G`
    
1 ≈90%    910.50M  1.78G  3.56G  7.11G  14.22G
    
2 ≈50%    455.50M  910.50M  1.78G  3.56G  7.11G
    
3 ≈33%    304.00M  608.00M  1.19G  2.37G  4.74G
    
4 ≈25%    228.00M  455.50M  910.50M  1.78G  3.56G

OpenJ9

關於OpenJ9的的詳細介紹你能夠從這裏瞭解更多。 對於內存利用率OpenJ9的策略是優於OpenJdk的。如下是OpenJ9的策略表格

容器內存<size>    最大Java堆大小
    
小於1 GB50%<size>
    
1 GB - 2 GB<size> - 512 MB
    
大於2 GB    大於2 GB
結論

注意:這裏咱們說的是容器內存限制,和物理機內存不一樣,

自動檔

若是你想要的是,不顯示的指定-Xmx,讓Java進程自動的發現容器限制。 

1.若是你想要的是jvm進程在容器中安全穩定的運行,不被容器kill,而且你的JDK版本小於10(大於等於JDK10的版本不須要設置,參考前面的測試) 你須要額外設置JVM參數-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap,便可保證你的Java進程不會由於內存問題被容器Kill。 固然這個方式使用起來簡單,可靠,缺點也很明顯,資源利用率太低(參考前面的表格MaxRAMFraction=4)。

2.若是想在基礎上我還想提升一些內存資源利用率,而且容器內存爲1 GB - 4 GB,我建議你設置-XX:MaxRAMFraction=2,在大於8G的能夠嘗試設置-XX:MaxRAMFraction=1(參考上表格)。

手動擋

若是你想要的是手動擋的體驗,更加進一步的利用內存資源,那麼你可能須要回到手動配置時代-Xmx。 手動擋部分,請能夠徹底忽略上面個人BB。 

1.上面的咱們說到了自動擋的配置,用起來很簡單很舒服,自動發現容器限制,無需擔憂和思考去配置-Xmx。

2.好比你有內存1G那麼我建議你的-Xmx750M,2G建議配置-Xmx1700M,4G建議配置-Xmx3500-3700M,8G建議設置-Xmx7500-7600M, 總之就是至少保留300M以上的內存留給JVM的其餘內存。若是堆特別大,能夠預留到1G甚至2G。 

3.手動擋用起來就沒有那麼舒服了,固然資源利用率相對而言就更高了。

原文:https://qingmu.io/2018/12/17/...

相關文章
相關標籤/搜索