前提

公司的 Java 服务都是跑在 Kubernetes 中的并且给其Pod配置的为 1C 1G,有一天发现一个服务突然提示 OOM ,那么我给 Pod 配置为 1C 2G 然后继续观察,然后发现还是有问题,Java 程序突然变成假死了.

部署方案

image-20240911105213054.png

描述

Java和docker并不是天然的朋友,docker可以设置内存和CPU限制,底层通过Linux cgroup技术实现,但是Java JVM并不能自动检测到。

在旧版本 Java8.131 JVM的可用内存和CPU数量并不是docker允许你使用的可用内存和CPU数量.

比如docker容器中限制只能使用1G,但是旧版本Java并不能识别到这个限制,当业务增长时,JVM就会申请更多内存,可能远超这个限制。但是如果使用太多内存,docker就会采取行动并杀死容器内的Java进程,显然这不是我们想要的!

目前我们生产环境使用Java8版本,这个问题可通过-Xmx限制堆内存大小来解决,不过这里实际限制了两次,一次是docker容器的内存限制,一次是jvm堆内存的限制。

解决方案

Java 1.8.131

修改 DockerFile

FROM adoptopenjdk/openjdk8
MAINTAINER peixinyi
# 设置容器环境变量
ARG SOFTWARE_VERSION
ARG SOFTWARE_VERSION_DATE
ENV SOFTWARE_VERSION ${SOFTWARE_VERSION}
ENV SOFTWARE_VERSION_DATE ${SOFTWARE_VERSION_DATE}
​
# 拷贝jar包
ENV JAR_PATH jar/app.jar
# 拷贝执行Jar包
COPY $JAR_PATH app.jar
# 设置配置文件路径
ENV CONFIG_PATH /resources/application.yaml
ENV RUN_JAR_PATH app.jar
​
ENV JVM_ARGS=${JVM_ARGS}
# 设置时区
RUN echo 'Asia/Shanghai' >/etc/timezone
# 暴露端口
EXPOSE 8080
# 挂载配置文件
VOLUME /resources
# 执行Jar包
ENTRYPOINT ["sh","-c","java $JVM_ARGS -jar $RUN_JAR_PATH --spring.config.location=$CONFIG_PATH"]

修改 Helm

# /charts/service/templates/deployment.yaml
...
env:
  - name: JVM_ARGS
    value: {{ .Values.jvmArgs }}
...

修改 Values

jvmArgs: "-Xms256m -Xmx1024m"

这里设置最小堆内存为512m,最大内存为1024m, 堆内存调整不要一味简单增大,要仔细分析内存占用过大的原因,是否有代码上的问题。

java 1.9+

 -XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap

强制JVM检查Linux的cgoup配置,实际上docker正是通过Linux的cgroup技术来限制容器的内存等资源的。现在如果应用达到了docker设置的限制(比如1G),JVM是可以看到这个限制的,JVM就会尝试GC操作。

如果gc之后仍然超过内存限制,那JVM就会做它该做的事情,比如抛出OutOfMemoryException.也就是说,JVM能够识别到docker的这些设置。

打印 GC 日志以及 OOM 自动 Dump

- name: JVM_ARGS
  value: -Xmx1536m -Xms512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/my-heap-dump.hprof -Xloggc:/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps

参数含义

命令

描述

-XX:+HeapDumpOnOutOfMemoryError

发生内存溢出自动dump内存文件

-XX:HeapDumpPath=/logs/my-heap-dump.hprof

指定dump文件地址为服务打印日志文件夹

-XX:+PrintGCDetails

输出详细GC日志

-XX:+PrintGCDateStamps

格式化输出时间戳

-Xloggc:/logs/gc.log

打印服务gc日志

参考

  1. docker容器化下的JVM参数调优 - https://blog.csdn.net/u012809308/article/details/111561850)