当前位置: 首页 > 科技观察

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

时间:2023-03-17 18:23:15 科技观察

JVM启动后,最大使用堆大小默认设置为物理内存的四分之一。例如,如果一个普通的x86服务器配置了128G内存,那么在容器中启动JVM会将最大允许堆内存调整为32G。如果JVM在容器启动时设置为只允许4G内存,那么当JVM使用超过4G内存时,内核就会kill掉JVM。测试代码如下:importJAVA.util.ArrayList;importJAVA.util.List;publicclassMemEat{publicstaticvoidmain(String[]args){Listl=newArrayList<>();while(true){byteb[]=newbyte[1048576];l。add(b);Runtimert=Runtime.getRuntime();System.out.println("freememory:"+rt.freeMemory());}}}代码很简单,就是无限循环不断申请内存,if在JAVA8u111版本之前,通过dockerrun-m100m限制内存使用100M时,运行一段时间后直接被kernelkill掉。输出如下:#JAVAMemEat...freememory:1307309488freememory:1306260896freememory:1305212304freememory:1304163712freememory:1303115120Killed为了避免这种情况,可以通过“-Xmx”设置最大堆内存,然后再次运行。#JAVA-Xmx100mMemEat...freememory:8382264freememory:7333672freememory:6285080freememory:5236488Exceptioninthread"main"JAVA.lang.OutOfMemoryError:JAVAheapspaceMemEat.main(MemEat.JAVAexitedduetoinsufficientheapmemory.你可以看到这种给JVM加参数的方法有一个缺点:如果修改了容器的内存限制,启动参数也需要调整。为此在JAVA8u144版本之后加入了动态调整功能,可以根据用户设置的内存限制进行动态调整。启动参数如下:#JAVA-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeapMemEat当我们修改内存参数时,JVM会相应调整。JAVA对容器的支持不断增强。最新的JAVA10版本之后,已经原生支持容器环境,无需添加任何参数。不仅如此,新版JAVA10还支持容器内CPU的动态调整。JVM调整内存最大heap如下:#dockerrun-it-m1024M--entrypointbshopenjdk:11-jdk#java-XX:+PrintFlagsFinal-version|grepMaxHeapSizesize_tMaxHeapSize=268435456可以看到上面的最大heap调整为四分之一内存限制一,不是物理内存的四分之一。也可以支持CPU自适应,如下图:#dockerrun-it--CPUs2---entrypointbashopenjdk:11-jdkjshell>Runtime.getRuntime().availableProcessors()$1==>2可以通过JAVA看到API成功获取当前设置的CPU数量。如果想用其他编程语言获取容器的CPU和内存限制,可以使用容器中的cgroup文件系统,比如获取容器内存的限制:#cat/sys/fs/cgroup/memory/memory.limit_in_bytes104857600【编者推荐】EasyUI+Maven+Shiro-SSM权威项目实战之Java基础与项目实战【天眼】Java大规模分布式跟踪监控系统面试官:说说你对Reactrefs的理解?应用场景?Java并发编程系列之一Thread简介