一、当前问题

1. JVM无法识别CGroup资源限制

Java 在容器化之后, jvm 感知的仍然是宿主机的内存 CPU, 默认情况下 JVM Xmx(最大堆大小)为宿主机内存的1/4

可以使用以下例子说明

docker 版本:18.06.0

jdk版本:1.8.0_181

➜  ~ docker --version
Docker version 18.06.0-ce, build 0ffa825
➜  ~ docker run -ti -m 512M openjdk:8u181-jdk
root@a069edb03118:/# java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
    uintx MaxHeapSize                              := 524288000                           {product}
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)
root@a069edb03118:/# exit
exit
➜  ~ docker run -ti -m 1024M openjdk:8u181-jdk
root@28478333dd17:/# java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
    uintx MaxHeapSize                              := 524288000                           {product}
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)
root@28478333dd17:/#

JDK 1.8.0_181 版本没法识别容器的资源限制, 不管对容器内存做什么资源限制, 容器内JVM 默认的最大内存大小仍然是宿主机内存1/4。

2. 使用错误的方式启动

当前部分Java镜像的启动脚本将 最大内存设为一个固定值: 例如

java -jar main.jar -Xms1g -Xmx3g

使用这种方式没法实现容器的扩容, 或者说给此类型的容器分配更高内存是没意义的

二、Java的支持

1. 支持识别容器的资源限制

新的Java版本(10及以上版本)已经内置了docker支持功能。但有时升级不是办法,比如说如果应用程序与新JVM不兼容就不行。Docker支持还被向后移植到Java 8。不妨检查标记为8u212的最新openjdk镜像。

我们可以验证一下:

docker 版本:18.06.0

jdk版本:1.8.0_212 (可识别CGroup)

➜  ~ docker run -ti -m 512M openjdk:8u212-jdk
root@55bf048f49ff:/# java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
    uintx MaxHeapSize                              := 134217728                           {product}
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b01-1~deb9u1-b01)
OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
root@55bf048f49ff:/# exit
exit
➜  ~ docker run -ti -m 1024M openjdk:8u212-jdk
root@ec63839d20e3:/# java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
    uintx MaxHeapSize                              := 268435456                           {product}
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b01-1~deb9u1-b01)
OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
root@ec63839d20e3:/#

可以看到随着容器内存限制的大小变化,JVM 默认最大堆大小的变化

2. MaxRAMFraction 参数

引用 https://www.kubernetes.org.cn/5005.html

配合另一个JVM参数来配置最大堆。-XX:MaxRAMFraction=int。下面是整理的一个常见内存设置的表格, 从中我们可以看到似乎JVM默认的最大堆的取值为MaxRAMFraction=4,随着内存的增加,堆的闲置空间越来越大,在16G容器内存时,java堆只有不到4G。

image-20190507174557328 hug 参考上边资料,我们来验证一下

docker 版本:18.06.0

jdk版本:1.8.0_212 (可识别CGroup)

➜  ~ docker run -ti -m 1024M openjdk:8u212-jdk java \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseCGroupMemoryLimitForHeap \
  -XX:MaxRAMFraction=1 -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 989.88M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b01-1~deb9u1-b01)
OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
➜  ~ docker run -ti -m 512M openjdk:8u212-jdk java \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseCGroupMemoryLimitForHeap \
  -XX:MaxRAMFraction=1 -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 494.94M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b01-1~deb9u1-b01)
OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
➜  ~

结果是比较符合的预期,JVM 可使用的最大内存可接近容器的内存限制,可以解决上述提出的问题一、二

三、镜像改造

对于我们当前的Java镜像,需要做两个变更:

  1. 修改JDK 版本——> 1.8.0_212 (针对java8)
  2. 启动参数不设置Xmx,改成设置MaxRAMFraction

四、参考文档