Java Spring 应用在 kubernetes 环境下JDK内存配置采坑
1. 前言
-
背景:
-
在微服务架构中,网关、注册中心、配置中心、服务追踪、认证中心等一系列组件部署到服务器中会占用一定的内存,还有各个业务服务,一部署单个服务可能就占个几百M,甚至上G。那这一系列组件和服务同时部署不得消耗更多的内存?为防止这些服务把系统资源耗尽导致宕机,我们不得不为这些服务配置一定的内存限制。
-
在 kubernetes 环境中,如果我们单单配置了memory的limit ,没有配置 Java 应用的JVM参数,那么在 Java 程序运行过程中可能会导致内存超过memory的limit,发送 OOM 错误。因此,部署Java 应用到 k8s 环境中时要配置JVM参数及k8s的memory的limit。
-
-
解决方案:
-
其中
MaxRAMPercentage
、InitialRAMPercentage
、MinRAMPercentage
这三个参数是JDK8u191
为适容器环境新增的参数,类比Xmx、Xms
,至于-XX:InitialRAMFraction
、-XX:MaxRAMFraction
、-XX:MinRAMFraction
已经被标记为deprecated 。那么这几个参数有什么好处呢? -
在容器环境下,可以给每个JVM实例所属POD分配任意大小的内存上限。如,给每个AccountService分配4G,给每个PaymentService分配8G。如此一来启动脚本就不好写成通用的了,设置3G也不是设置6G也不是。但有了这几个参数,就可以在通用的启动脚本中指定75%,如:
-XX:InitialRAMPercentage=75 -XX:MinRAMPercentage=75 -XX:MaxRAMPercentage=75
,那么AccountService就相当于设置了-Xmx3G -Xms3G
,而PaymentService相当于设置了-Xmx6G -Xms6G
,是不是很赞。
-
2. JVM 配置差异
- 随着 JAVA 版本不同,JVM 启动参数配置有所不同,如下表:
JDK版本 | JVM参数 |
---|---|
<8u131 | -Xms64m -Xmx128m |
8u131-191 | -XX:+UnlockExperimentalVMOptions、-XX:+UseCGroupMemoryLimitForHeap |
>8u191 | -XX:UseContainerSupport(默认启用)、ActiveProcessorCount;百分比分配堆内存:MaxRAMPercentage、InitialRAMPercentage、MinRAMPercentage |
-
JDK8 版本小于
131
时,启动JAVA程序时,添加参数-Xms64m -Xmx128m
参数,在java 8u131+
和java 9+
版本,添加两个参数:-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
,在 JDK8 版本高于191
时,可以使用MaxRAMPercentage
,其值介于 0.0 到 100.0 之间,默认值为 25.0。 -
查看JVM所有支持的参数及默认值(>
JDK6 update 21
才支持):java -XX:+PrintFlagsFinal
3. 配置实践
3.1 JDK 版本8u131
- 在
Dockerfile
文件中配置:
ENV JVM_OPTS -Xms256m -Xmx512m
ENTRYPOINT exec java $JVM_OPTS -jar lanweihong.jar
- 注:这两个参数只设置了分配给堆的大小,实际的memory limit应该比这个还要大。
3.2 java 8u131+
和java 9+
版本
- 对于
java 8u131+
和java 9+
版本,在Dockerfile
文件中,设置环境变量:
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等)没有可用内存。
3.3 JDK版本 8u191+
- 在
Dockerfile
文件中配置:
ENV JVM_OPTS -Xms256m -Xmx512m
ENTRYPOINT exec java $JVM_OPTS -jar lanweihong.jar
- 设置 JVM 可用的内存为总内存的 80%,显然这种方式更加灵活方便,也更安全。
如果使用 jib-maven-plugin 打包的,可以在 pom.xml 中添加配置:
<container>
<mainClass>com.lanweihong.gateway.GatewayApplication</mainClass>
<!-- 添加 jvm 参数 -->
<jvmFlags>
<jvmFlag>-XX:MaxRAMPercentage=80.0</jvmFlag>
</jvmFlags>
</container>
- 执行效果等同于
java -XX:MaxRAMPercentage=80.0
3.4 Kubernetes 配置
- 设置内存 limit 在 k8s 部署配置文件添加:
containers:
resources:
requests:
memory: "512Mi"
# cpu: "500m"
limits:
memory: "512Mi"
# cpu: "500m"
env:
- name: JVM_OPTS
values: -XX:MaxRAMPercentage=80.0
- 使用
kubectl apply -f
更新发布服务。
4. 总结
-
当 JDK8 版本 <
191
的建议升级到 >=191
,>=Java 8u191
和Java 10+
支持-XX:UseContainerSupport
和参数-XX:MaxRAMPercentage
,具体值需根据情况而定。 -
通常可使用的 java 镜像有:
-
openjdk:8-jre-alpine
应该是使用最广泛的,生产案例: https://github.com/wl4g/dopaas-iam/blob/3.0.0/services/service-starter-web/Dockerfile -
registry.access.redhat.com/ubi8/ubi
生产案例: https://github.com/sonatype/docker-nexus3/blob/master/Dockerfile
-