在Openjdk 8 中如何合理使用容器 memory 資源

豬齒魚效能平臺 發表於 2021-10-23

 前言

將 Java 應用容器化雖然更好地解決了可移植性問題,但也存在著一些不友好的情況,比如低版本的JDK(低於Java 8u131)並不能識別 CGroup 資源限制。這將導致JVM讀取的是宿主機的全部CPU和記憶體,一但容器使用資源超過限制則會被 docker 殺死。

在 kubernetes 中,我們會顯示在 yaml 檔案中配置CPU、記憶體請求和限制,我們希望容器中的JVM程式能夠自動識別到 CGroup 資源限制,獲取到正確的記憶體和CPU資訊從而自行動態調整。

JVM 引數配置

以下操作皆在一臺 4C 16G 伺服器上進行。

版本低於 8u131

JDK 版本低於 8u131 版本的 JVM 不會自動識別到 CGroup 資源限制,需要手動設定初始堆大小以及最大堆大小,否則會按照宿主機的全部記憶體設定預設值:

  • 配置最大堆大小 -Xmx,預設值:記憶體的1/4
  • 配置初始堆大小 -Xms,預設值:記憶體的1/64

未配置JVM引數

可以看到 Max. Heap Size (Estimated): 3.48G,未能正確識別 CGroup 資源限制

$ docker run --rm -m 2GB openjdk:8u121-alpine java -XshowSettings:vm -version

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

openjdk version "1.8.0_121"
OpenJDK Runtime Environment (IcedTea 3.3.0) (Alpine 8.121.13-r0)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

配置JVM引數

配置 -Xmx 和 -Xms 後即可達到我們想要的結果

$ docker run --rm -m 2GB openjdk:8u121-alpine java -XshowSettings:vm -Xmx2000m -Xms2000m -version

VM settings:
    Min. Heap Size: 1.95G
    Max. Heap Size: 1.95G
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_121"
OpenJDK Runtime Environment (IcedTea 3.3.0) (Alpine 8.121.13-r0)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

8u131 及以上版本

從 8u131 版本開始支援 UseCGroupMemoryLimitForHeap 和 MaxRAMFraction 這兩個選項,用 CGroupMemory 的大小作為 JVM heap size,MAXRAMFraction 是用來控制實際可用的記憶體數量的,比如設定為 1 的話就是 CGroupMemoryLimit 的全部,設定為 2 的話一半,3 的話就是 1/3,以此類推

|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|

未配置JVM引數

可以看到 Max. Heap Size (Estimated): 3.48G,未能正確識別 CGroup 資源限制

$ docker run --rm -m 2GB openjdk:8u131-alpine java -XshowSettings:vm  -version

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

openjdk version "1.8.0_131"
OpenJDK Runtime Environment (IcedTea 3.4.0) (Alpine 8.131.11-r2)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

配置JVM引數

配置 -XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap 和 -XX:MaxRAMFraction=1 後即可達到我們想要的結果

$ docker run --rm -m 2GB openjdk:8u131-alpine java -XshowSettings:vm -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -version

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

openjdk version "1.8.0_131"
OpenJDK Runtime Environment (IcedTea 3.4.0) (Alpine 8.131.11-r2)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

8u191 及以上版本

從 8u191 開始引入了 java10+ 上的 UseContainerSupport 選項,而且是預設啟用的,不用設定。同時 UseCGroupMemoryLimitForHeap 這個就棄用了,不建議繼續使用,同時還可以通過 -XX:InitialRAMPercentage-XX:MaxRAMPercentage-XX:MinRAMPercentage 這些引數更加細膩的控制 JVM 使用的記憶體比率。比如一些 Java 程式在執行時會呼叫外部程式、申請 Native Memory 等,所以即使是在容器中執行 Java 程式,也得預留一些記憶體給系統的。所以 -XX:MaxRAMPercentage 不能配置得太大。

未配置JVM引數

可以看到未新增任何 JVM 引數即可正確識別到 CGroup 資源限制

$ docker run --rm -m 2GB openjdk:8u191-alpine java -XshowSettings:vm -version

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

openjdk version "1.8.0_191"
OpenJDK Runtime Environment (IcedTea 3.10.0) (Alpine 8.191.12-r0)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

配置JVM引數

  • 使用 -XX:MaxRAMFraction 引數調整 Max. Heap Size 大小 `console $ docker run –rm -m 2GB openjdk:8u191-alpine java -XX:MaxRAMFraction=1 -XshowSettings:vm -version

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

openjdk version “1.8.0_191” OpenJDK Runtime Environment (IcedTea 3.10.0) (Alpine 8.191.12-r0) OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)


* 使用 `-XX:InitialRAMPercentage`、`-XX:MaxRAMPercentage`、`-XX:MinRAMPercentage` 引數更加細膩的控制 JVM 使用的記憶體比率
```console
  $ docker run --rm -m 2GB openjdk:8u191-alpine java -XX:InitialRAMPercentage=40.0 -XX:MaxRAMPercentage=90.0 -XX:MinRAMPercentage=50.0 -XshowSettings:vm -version
  
  VM settings:
      Max. Heap Size (Estimated): 1.60G
      Ergonomics Machine Class: server
      Using VM: OpenJDK 64-Bit Server VM
  
  openjdk version "1.8.0_191"
  OpenJDK Runtime Environment (IcedTea 3.10.0) (Alpine 8.191.12-r0)
  OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

參考資料


本文由豬齒魚技術團隊原創,轉載請註明出處:豬齒魚官網

關於豬齒魚

豬齒魚Choerodon全場景效能平臺,提供體系化方法論和協作、測試、DevOps及容器工具,幫助企業拉通需求、設計、開發、部署、測試和運營流程,一站式提高管理效率和質量。從團隊協同到DevOps工具鏈、從平臺工具到體系化方法論,豬齒魚全面滿足協同管理與工程效率需求,貫穿端到端全流程,助力團隊效能更快更強更穩定。戳此處試用豬齒魚