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

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

[[409725]]

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

  1. import JAVA.util.ArrayList; 
  2. import JAVA.util.List; 
  3.  
  4. public class MemEat { 
  5.     public static void main(String[] args) { 
  6.         List l = new ArrayList<>(); 
  7.         while (true) { 
  8.             byte b[] = new byte[1048576]; 
  9.             l.add(b); 
  10.             Runtime rt = Runtime.getRuntime(); 
  11.             System.out.println( "free memory: " + rt.freeMemory() ); 
  12.         } 
  13.     } 

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

  1. # JAVA MemEat 
  2. . . . 
  3. free memory: 1307309488 
  4. free memory: 1306260896 
  5. free memory: 1305212304 
  6. free memory: 1304163712 
  7. free memory: 1303115120 
  8. Killed 

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

  1. # JAVA -Xmx100m MemEat 
  2. . . . 
  3. free memory: 8382264 
  4. free memory: 7333672 
  5. free memory: 6285080 
  6. free memory: 5236488 
  7. Exception in thread "main" JAVA.lang.OutOfMemoryError: JAVA heap space MemEat.main(MemEat.JAVA:8) 

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

  1. # JAVA -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap MemEat 

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

  1. # docker  run -it -m 1024M --entrypoint bash openjdk:11-jdk    
  2. # java -XX:+PrintFlagsFinal -version | grep MaxHeapSize 
  3.   size_t MaxHeapSize = 268435456                

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

  1. # docker  run -it --CPUs 2 ---entrypoint bash openjdk:11-jdk 
  2. jshell> Runtime.getRuntime().availableProcessors() 
  3. $1 ==> 2 

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

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

  1. # cat /sys/fs/cgroup/memory/memory.limit_in_bytes 
  2. 104857600 

 【编辑推荐】

 

责任编辑:武晓燕 来源: 今日头条
相关推荐

2010-11-19 17:01:52

2020-09-02 07:20:48

KubernetesJava应用程序容器

2010-08-19 16:25:04

DB2 CMO

2022-02-15 09:36:13

容器应用程序云服务

2010-08-13 15:35:05

2010-10-21 13:14:35

2012-08-07 09:37:23

虚拟化

2011-04-25 09:35:31

TwitterJava

2011-04-27 09:39:53

EclipseIntelliJ

2019-01-07 08:10:54

微服务单体 Web

2018-07-04 14:17:10

微服务代码开发

2023-08-08 21:03:52

2018-07-10 14:46:04

LinuxShellsudo

2018-12-19 09:20:45

Linux迁移安装软件

2018-01-08 08:50:05

Linux内核系统程序

2010-09-29 11:06:21

活动目录OpenLDAP

2022-10-10 08:05:34

线程池OOM问题

2010-07-23 16:34:30

云计算厂商

2014-12-10 11:13:41

谷歌微软Windows应用

2021-03-18 08:01:52

Docker容器迁移
点赞
收藏

51CTO技术栈公众号