1、JVM堆内存设置

      跑在docker容器的Java服务发生过几次内存超标异常,实际上这和Java程序的docker容器化有很大的关系。Java和docker并不是天然的朋友,docker可以设置内存和CPU限制,底层通过Linux cgroup技术实现,但是Java JVM并不能自动检测到。

我们可以使用Java的Xmx标识手动指定堆内存的大小或者使用较高版本的JDK提供的JVM标识,解决这个问题。

问题:旧版本Java8(update 131之前的版本),JVM的可用内存和CPU数量并不是docker允许你使用的可用内存和CPU数量

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

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

解决方法:

这个前提需要Java程序的dockerfile支持:

# 初始镜像
FROM adoptopenjdk/openjdk8
# jar包名字需要更改为项目名字-版本号,后面app.jar 不变
ADD  example-sun-1.0.jar app.jar
# 配置JVM启动参数
ENV JVM_ARGS=${JVM_ARGS}
EXPOSE 8080
# 优化jvm参数配置启动
ENTRYPOINT java ${JVM_ARGS} -Djava.security.egd=file:/dev/./urandom -jar app.jar

具体在k8s deployment.yaml 部署文件中环境变量 env 中加入以下参数,当JVM 启动时就会加载进去

- name: JVM_ARGS
  value: -Xmx1024m -Xms512m

-Xmx1024m #设置jvm堆内存的最大值

-Xms512m  #设置jvm堆内存的最小值

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

较高版本Java9之后(8u131+)JVM提供更好的解决方式

      使用JVM 标志: -XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap

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

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

2、GC日志打印以及OOM自动dump

程序运行过程中,也可以打印GC日志,方便排查问题, 同时当Java发生OutOfMemory 异常时可将heap 内存 dump 下来方便我们排查问题使用,设置以下参数:

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

参数含义:

# 发生内存溢出自动dump内存文件
-XX:+HeapDumpOnOutOfMemoryError
############ 指定dump文件地址为服务打印日志文件夹/logs(已挂载)
-XX:HeapDumpPath=/logs/my-heap-dump.hprof
############ 打印服务gc日志
-Xloggc:/logs/gc.log
# 输出详细GC日志
-XX:+PrintGCDetails
# 格式化输出时间戳 2020-09-17T19:45:05.680+0800
-XX:+PrintGCDateStamps

想了解更多关于docker、k8s等云原生以及Java干货,欢迎关注下方公众号:

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐