本文主要描述在火山引擎容器服务(VKE) 和弹性容器实例(VCI)业务中合理定义 Java 8 堆栈内存的最佳实践。
在部署 Java 应用的时候,一个关键的操作是定义 JVM 虚拟机可以使用的内存大小,即最大堆栈内存(MaxHeapSize)。在比较原始的虚拟机/物理机部署模式下,通过命令行参数-Xmx
、-Xms
以及-Xss
来定义堆栈内存,这是由于虚拟机/物理机一般情况下不会调整资源(如内存)上限,因此可以使用一条固化的命令来启动应用。但在 Kubernetes 环境中 Pod 的资源上限可以轻易变更,因此上述手动命令行指定堆栈内存的方式不太适用。
在 Kubernetes 环境中,用户通常希望 JVM 虚拟机可以根据 Pod 的spec.containers.resources.limits.memory
参数值,自动分配合理的 MaxHeapSize,因此容器服务(VKE)和弹性容器实例(VCI)为用户提供了合理定义 Java 8 堆栈内存方案。
随着 Kubernetes 的普及,Java 本身也在不断优化自身能力来适配前者。Java 官方在不同 JDK(Java 开发工具包)版本中提供了几种方式来自动获取容器环境中的资源上限来配置自身最大堆栈内存的方式。本文将在不同的 JDK 版本中进行实验,为您解答各配置方式能否正常运行 VKE 和 VCI 业务 Pod。
在实验之前,可以通过如下命令,获取当前环境中最大堆栈内存大小:
$ java -XshowSettings:vm -version VM settings: Max. Heap Size (Estimated): 479.50M Ergonomics Machine Class: client Using VM: OpenJDK 64-Bit Server VM openjdk version "1.8.0_252" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_252-b09) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.252-b09, mixed mode)
上方代码中Max. Heap Size (Estimated)
的值,表示最大堆栈内存大小。
在 JDK 8u131 之前的版本中,运行在容器中的 Java 进程无法自动感知到 Kubernetes 为容器设置的内存上限, 因此无法自动化设定合理的堆栈内存值,只能通过-Xmx
、-Xms
以及-Xss
命令手动指定。
在 JDK 8u131 版本中,添加了一个实验性的参数UseCGroupMemoryLimitForHeap
,配合UnlockExperimentalVMOptions
参数来完成对容器内存上限的感知,并自动配置合理的堆栈内存值。
说明
UseCGroupMemoryLimitForHeap
参数在 JDK 8u191 之后已经有了更好的替代方式,除非必须使用 JDK 8u131 至 8u191 之间的版本,否则不推荐使用该参数定义 Java 8 堆栈内存。UseCGroupMemoryLimitForHeap
参数仅支持 Linux cgroup V1 版本,不支持 cgroup v2 版本,而集群中以 VCI 方式部署的应用使用了 cgroup v2 版本,因此该方案不适用于 VCI 业务应用。从 JDK 8u191 版本开始,Java 官方提供了正式特性UseContainerSupport
来提供容器内存上限自动感知的能力。该特性为默认开启特性,因此无需在 Java 启动命令中添加该参数。
满足 Java 版本要求的 VKE 集群环境下,在某个以云服务器 ECS 方式部署的应用容器(其容器内存上限为 2048 MiB)中执行如下命令,获取当前环境最大堆栈内存大小,返回结果如下:
$ java -XshowSettings:vm -version Picked up JAVA_TOOL_OPTIONS: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap VM settings: Max. Heap Size (Estimated): 494.94M Ergonomics Machine Class: server Using VM: OpenJDK 64-Bit Server VM openjdk version "1.8.0_192" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_192-b12) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.192-b12, mixed mode)
从返回结果得知:最大堆栈内存大小为 494.94 MiB,容器内存上限为 2048 MiB,即最大堆栈内存约为容器内存上限的 1/4,1/4 是一个默认设定的百分比,您可以自行调整比例,详情请参见本文下方 附录。
说明
该版本的 JDK 只是自动的设置了-XX+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
,JDK 8u191-8u192 版本依然无法在 cgroup v2 环境中自动感知容器内存上限,因此该方案也不适用于 VCI 业务应用。
从 JDK 8u372 版本开始,Java 官方提供的UseContainerSupport
特性适配了 cgroup v2。
满足 Java 版本要求的 VKE 集群环境下,在某个以 VCI 方式部署的应用容器(其容器内存上限为 2048 MiB)中执行如下命令,获取当前环境最大堆栈内存大小,返回结果如下:
$ java -XshowSettings:vm -version VM settings: Max. Heap Size (Estimated): 479.50M Ergonomics Machine Class: client Using VM: OpenJDK 64-Bit Server VM openjdk version "1.8.0_201" OpenJDK Runtime Environment (IcedTea 3.11.0) (Alpine 8.201.08-r1) OpenJDK 64-Bit Server VM (build 25.201-b08, mixed mode)
相比 8u191 <= Java 版本 <= 8u192 中的实验而言,VCI 环境中感知到的容器内存上限比 VKE 环境中稍小,故而自动设置的最大堆栈内存也少了一点,这符合 MicroVM 的技术原理。
为了能够得到在 ECS 方式部署的应用(又称标准容器)和 VCI 方式部署的应用(又称弹性容器)中同时生效的容器内存自动感知能力,本文将不同的 JDK 版本、命令参数进行组合,分别在标准容器和弹性容器环境中测试,观察命令是否可以顺利运行,以及分配的最大内存堆栈是否正确,实验结论如下:
说明
测试环境中的 Pod 均分配了spec.containers.resources.limits.memory
为 2048 MiB,VKE 节点内存为 32 GiB。
JDK版本 | 命令行参数 | 标准容器 | 弹性容器 | 测试结果 | ||
---|---|---|---|---|---|---|
正常运行 | MaxHeapSize | 正常运行 | MaxHeapSize | |||
8u131 | UnlockExperimentalVMOptions UseCGroupMemoryLimitForHeap | 是 | 455.50M | 否 | 无 | 不通过 |
不加参数 | 是 | 6.80G | 是 | 479.50M | 不通过 | |
8u191 | UnlockExperimentalVMOptions UseCGroupMemoryLimitForHeap | 是 | 494.94M | 否 | 无 | 不通过 |
UseContainerSupport | 是 | 494.94M | 否 | 无 | 不通过 | |
8u372 | UnlockExperimentalVMOptions UseCGroupMemoryLimitForHeap | 是 | 494.94M | 否 | 无 | 不通过 |
UseContainerSupport | 是 | 494.94M | 是 | 479.50M | 通过 |
根据测试结果,为了让容器内存自动感知能力能够同时在标准容器和弹性容器环境中正常工作时,您需要 同时 满足以下条件:
UnlockExperimentalVMOptions
和UseCGroupMemoryLimitForHeap
这一组过时参数组合,也不需要在启动参数中添加其他参数,请保持UseContainerSupport
默认开启即可。下文也为您介绍其他堆栈内存设置相关参数。
在 OpenJDK 环境中,能自动感知容器内存的情况下,系统默认会以容器内存上限的 1/4 作为最大堆栈内存值。您可以通过MaxRAMFraction
参数来调整分母。例如设置为 2 ,可以将容器内存上限的 1/2 作为最大堆栈内存值。
$ java -XX:MaxRAMFraction=2 -XshowSettings:vm -version VM settings: Max. Heap Size (Estimated): 957.00M Ergonomics Machine Class: client Using VM: OpenJDK 64-Bit Server VM openjdk version "1.8.0_201" OpenJDK Runtime Environment (IcedTea 3.11.0) (Alpine 8.201.08-r1) OpenJDK 64-Bit Server VM (build 25.201-b08, mixed mode)
在 Oracle JDK 中,可以使用MaxRAMPercentage
参数来替换MaxRAMFraction
参数,实现相同的能力。详细操作,请参见 Oracle 官方文档。