前提
公司的 Java 服务都是跑在 Kubernetes 中的并且给其Pod
配置的为 1C 1G
,有一天发现一个服务突然提示 OOM
,那么我给 Pod
配置为 1C 2G
然后继续观察,然后发现还是有问题,Java 程序突然变成假死了.
部署方案
描述
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
参数含义
参考
docker容器化下的JVM参数调优 - https://blog.csdn.net/u012809308/article/details/111561850)
评论