Kubernetes,  Spring & JVM

Java Spring 应用在 kubernetes 环境下JDK内存配置采坑

1. 前言

  • 背景:

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

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

  • 解决方案:

    • 其中MaxRAMPercentageInitialRAMPercentageMinRAMPercentage这三个参数是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:MaxRAMcgroup 的内存限制,这里的-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. 总结

5. 参考

留言

您的电子邮箱地址不会被公开。