Java/Spring应用在k8s环境中的内存配置实践

前言

在微服务架构中,网关、注册中心、配置中心、服务追踪、认证中心等一系列组件部署到服务器中会占用一定的内存,还有各个业务服务,一部署单个服务可能就占个几百M,甚至上G。那这一系列组件和服务同时部署不得消耗更多的内存?为防止这些服务把系统资源耗尽导致宕机,我们不得不为这些服务配置一定的内存限制。

在 k8s 环境中,如果我们单单配置了 memory 的 limit ,没有配置 Java 应用的JVM参数,那么在 Java 程序运行过程中可能会导致内存超过 memory 的 limit ,发送 OOM 错误。因此,部署Java 应用到 k8s 环境中时要配置JVM参数及k8s的 memory limit。

JVM 参数配置方法

随着JAVA版本不同,有不同的处理方法,如下表:

JDK版本JVM参数
<8u131-Xms64m -Xmx128m
8u131-191-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap
>8u191-XX:UseContainerSupport(默认启用)、ActiveProcessorCount;百分比分配堆内存:MaxRAMPercentageInitialRAMPercentageMinRAMPercentage

JDK8版本小于131时,启动JAVA程序时,添加参数 -Xms64m -Xmx128m;java 8u131+和java 9+版本,添加两个参数:-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap;JDK8版本高于191时,可以使用 MaxRAMPercentageMaxRAMPercentage 值介于 0.0 到 100.0 之间,默认值为 25.0。

配置

JDK版本8u131

Dockerfile 文件中配置:

1
2
ENV JVM_OPTS -Xms256m -Xmx512m
ENTRYPOINT exec java $JVM_OPTS -jar lanweihong.jar

注意这两个参数只设置了分配给堆的大小,实际的memory limit应该比这个还要大。

java 8u131+和java 9+版本

对于java 8u131+和java 9+版本,在 Dockerfile 文件中,设置环境变量:

1
2
ENV JVM_OPTS -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2
ENTRYPOINT exec java $JVM_OPTS -jar lanweihong.jar

其中 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 设置 -XX:MaxRAM 为 cgroup 的内存限制,这里的-XX:MaxRAMFraction 值为 2,那么 JVM 允许分配的内存为 MaxRAM / MaxRAMFraction = 4G / 2 = 2G,不设置为 1 是防止JVM占用所有的内存,导致其他进程(如Shell、MySQL等)没有可用内存。

JDK版本 8u191+

Dockerfile 文件中配置:

1
2
ENV JVM_OPTS -XX:MaxRAMPercentage=80.0
ENTRYPOINT exec java $JVM_OPTS -jar lanweihong.jar

设置 JVM 可用的内存为总内存的 80%,显然这种方式更加灵活方便,也更安全。

如果使用 jib-maven-plugin 打包的,可以在 pom.xml 中添加配置:

1
2
3
4
5
6
7
8
9
...
<container>
<mainClass>com.lanweihong.gateway.GatewayApplication</mainClass>
<!-- 添加 jvm 参数 -->
<jvmFlags>
<jvmFlag>-XX:MaxRAMPercentage=80.0</jvmFlag>
</jvmFlags>
</container>
...

执行效果等同于 java -XX:MaxRAMPercentage=80.0 ...

k8s 配置

设置内存 limit;在 k8s 部署配置文件添加:

1
2
3
4
5
6
7
8
9
10
11
containers:
resources:
requests:
memory: "512Mi"
# cpu: "500m"
limits:
memory: "512Mi"
# cpu: "500m"
env:
- name: JVM_OPTS
values: -XX:MaxRAMPercentage=80.0

使用 kubectl apply -f 更新发布服务。

总结

  1. JDK8版本低于191的建议升级到最新版本,或191以后,Java 8u191和Java 10+ 支持 UserContainerSupport,设置JVM启动参数 MaxRAMPercentage,具体的值根据情况来设置;
  2. 不升级JDK版本的,根据JDK版本按以上说明配置;
  3. Kubernetes 配置 Limit。

参考文档

  1. How To Configure Java Heap Size Inside a Docker Container
  2. Kubernetes_pod_javajdk_动态JVM堆内存大小限制