写点什么

为啥 Java 应用迁移到容器后会出现 OOM?

发布于: 4 小时前


JVM 启动后默认将最大使用堆大小设置为物理内存的四分之一,譬如一台普通的 x86 服务器配置 128G 内存,那么启动在容器的内启动 JVM 会将自己最大允许使用的堆内存调整为 32G 内存,如果容器启动时设置 JVM 只允许使用 4G 大小的内存,那么当 JVM 使用内存超过 4G 后,将会导致内核杀死 JVM。测试代码如下:

import JAVA.util.ArrayList;import JAVA.util.List;
public class MemEat { public static void main(String[] args) { List l = new ArrayList<>(); while (true) { byte b[] = new byte[1048576]; l.add(b); Runtime rt = Runtime.getRuntime(); System.out.println( "free memory: " + rt.freeMemory() ); } }}
复制代码

代码非常简单,只是通过一个死循环不停地申请内存,如果是在 JAVA 8u111 版本之前,直接通过 docker run -m 100m 限制使用 100M 内存的情况下,运行一段时间后直接被内核杀死。输出如下:

# JAVA MemEat. . .free memory: 1307309488free memory: 1306260896free memory: 1305212304free memory: 1304163712free memory: 1303115120Killed
复制代码

为了避免这种情况,可以通过“ -Xmx ”设置最大堆内存后再次运行

# JAVA -Xmx100m MemEat. . .free memory: 8382264free memory: 7333672free memory: 6285080free memory: 5236488Exception in thread "main" JAVA.lang.OutOfMemoryError: JAVA heap space MemEat.main(MemEat.JAVA:8)
复制代码

可以看到 JVM 由于堆内存不足,自己退出了。这种在 JVM 添加参数的方式有个弊端:如果修改了容器的内存限制,还需要调整启动参数。为此在 JAVA 8u144 版本之后添加了动态调整的功能,能够根据用户设定的内存限制动态调整,启动参数如下:

# JAVA -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap MemEat
复制代码

当我们修改了内存参数后 JVM 便可以随之调整。JAVA 对于容器的支持不断增强到最新的 JAVA 10 版本后,已经原生支持容器环境,无需添加任何参数。不仅如此,新版 JAVA 10 还支持 CPU 在容器内动态调整。如下所示 JVM 调整内存最大堆:

# docker  run -it -m 1024M --entrypoint bash openjdk:11-jdk   # java -XX:+PrintFlagsFinal -version | grep MaxHeapSize  size_t MaxHeapSize = 268435456                                 
复制代码

可以看到上面的最大堆调整到内存限制的四分之一,而非物理内存的四分之一。还可以支持 CPU 自适应,如下所示:

# docker  run -it --CPUs 2 ---entrypoint bash openjdk:11-jdkjshell> Runtime.getRuntime().availableProcessors()$1 ==> 2
复制代码

可以看到通过 JAVA 的 API 成功地获取到当前设置的 CPU 个数。

如果是其他编程语言希望获取到容器的 CPU 和内存限制,可以通过容器内的 cgroup 文件系统,如获取容器内存的限制:

# cat /sys/fs/cgroup/memory/memory.limit_in_bytes104857600
复制代码


用户头像

还未添加个人签名 2020.09.07 加入

还未添加个人简介

评论

发布
暂无评论
为啥Java应用迁移到容器后会出现OOM?