xxl-job导致容器OOM

这个问题困扰了我2个多月,现在终于找到了原因了。现在将分析过程以及原因记录下来。

背景

部署在容器中的一些Java程序每个月都会有1次由于超过容器的限制内存大小,被cgroup杀掉。 容器的内存限制是3G,Java程序的堆内存的最大大小是2G,内存多出了几百M。

初步分析

非堆内存大小调整

一开始JVM参数配置是这样子的:

ruby 复制代码
-Xmx2G -Xms2G 
-XX:+UseG1GC -XX:G1HeapRegionSize=16m

由于不是JVM层面的内存溢出,所以堆内存配置是没问题。除了堆内存外,还有Metaspace、CodeCache、CompressedClassSpace、DirectByteBuffer这几块非堆内存,没有做限制。所以增加了非堆内存的限制:

ini 复制代码
-XX:CompressedClassSpaceSize=128m 
-XX:MaxMetaspaceSize=256m 
-XX:MaxDirectMemorySize=128m

增加了限制后,容器的内存依旧不断增大。查看非堆内存的监控,非堆内存并不会一直增大。所以也不是非堆内存的问题。

分析非JVM管理内存

分别打印内存增大之前和之后的pmap的内容,对比两次的内存情况。发现Java进程的RSS内存只差了7M左右,和实际情况不符合。其他部分的内存差异也不大。分析到这里,还是不能确定问题原因。 获取pmap的命令:

shell 复制代码
pmap -X ${pid} > pmap.txt

问题定位

发现规律

通过监控平台发现不出现这个问题的容器WSS内存和RSS内存差异不大,而经常OOM的容器WSS内存和RSS内存差异会不断增大,如下图: 那监控中的WSS和RSS指标分别代表什么呢?

分析原因

指标解析

RSS的指标是:container_memory_rss WSS的指标是:container_memory_working_set_bytes container_memory_rss是指不包括任何cache的进程自己的内存,取的是memory.stat文件中的anno的值。 container_memory_working_set_bytes是包含RSS、文件缓存内存、内存脏页、内核内存、slab内存等内存,简单计算就是(总内存-inactive_files内存),并且容器触发OOM Killer是由这个指标决定。当WSS内存超过容器内存限制大小后,就会触发OOM Killer。 那RSS和WSS是差了哪部分内存呢?

差异内存

进入容器执行命令,查看memory.stat:

shell 复制代码
# 没有这个文件,则打开下面的文件
cat /sys/fs/cgroup/memory.stat

如果没找到上面的文件,则查看下面的文件:

shell 复制代码
cat /sys/fs/cgroup/memory/memory.stat

内容如下:

yaml 复制代码
anon 5413322752
file 7618560
kernel 1010552832
kernel_stack 2965504
pagetables 12705792
percpu 0
sock 16384
vmalloc 0
shmem 0
zswap 0
zswapped 0
file_mapped 2818048
file_dirty 49152
file_writeback 0
swapcached 0
anon_thp 5188354048
file_thp 0
shmem_thp 0
inactive_anon 5423308800
active_anon 24576
inactive_file 5468160
active_file 2146304
unevictable 0
slab_reclaimable 992354280
slab_unreclaimable 2309568
slab 994663848
workingset_refault_anon 0
workingset_refault_file 33846
workingset_activate_anon 0
workingset_activate_file 817
workingset_restore_anon 0
workingset_restore_file 627
workingset_nodereclaim 0
pgscan 1517152
pgsteal 1515495
pgscan_kswapd 81139
pgscan_direct 1436013
pgsteal_kswapd 81139
pgsteal_direct 1434356
pgfault 2014894
pgmajfault 209
pgrefill 29369
pgactivate 27688
pgdeactivate 28594
pglazyfree 0
pglazyfreed 0
zswpin 0
zswpout 0
thp_fault_alloc 2162
thp_collapse_alloc 382

发现其中的slab_reclaimable占用的大小刚好跟WSS和RSS的差值差不多。 那slab_reclaimable又是什么含义呢?

SLAB

Linux有两种内存分配算法:伙伴系统(buddy system)和slab。在Linux中,伙伴系统是以页为单位管理和分配内存。slab分配器就应运而生了,专为小内存分配而生。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。 slab专用缓冲区主要用于内核频繁使用的一些数据结构,例如task_struct、mm_struct、vm_area_struct、file、dentry、inode等。 而slab_reclaimable就是slab分配的可被回收的内存,这部分内存在内存不足的时候,可以被系统回收。 但是现在在容器里,却导致了OOM Killer。 那是什么导致了slab_reclaimable使用增加呢?

xxl-job执行器的日志文件

由于slab是分配小内存的,既然能增长到这么大,说明有数量很多的小内存被分配。想到程序中使用了xxl-job,而xxl-job每次调度后,执行器executor都会生成一个单独的日志文件,记录当次的定时任务执行情况。 所以可能是因为频繁创建文件导致slab内存使用过大。

验证猜想

关闭xxl-job执行器日志

修改xxl-job-core的代码,注释掉原来创建文件的代码,改为logger.info方式打印内容: 修改xxl-job日志后,发现WSS和RSS的内存差值,运行一天依然稳定在20M左右。看来问题的确是出在xxl-job不断生产日志文件上。

程序验证

为了更全面的验证创建文件和slab的关系,编写一个程序不断创建小文件,每个文件大小固定在574字节。 场景一:写入50万文件,堆内存:1G,容器最大内存:1500M WSS和RSS差值到了330M 堆内存:1G,非堆:50M左右

场景二:写入500万文件,不删文件,缩小容器内存大小,模拟内存超过容器内存的场景。堆内存:1G,容器最大内存:1500M WSS和RSS差值最大到了317M,达到最高值后,在23点41分触发oom,被cgroup杀掉。

堆内存是1G,非堆保持在50m左右,和场景一差不多,所以OOM与JVM管理的内存无关

cgroup oom kill的系统日志如下,被杀掉时,java进程的RSS内存为1207198K(1178.9M),和容器最大内存1500M,相差322M。符合上图WSS-RSS监控的的差值。java进程的内存基本固定的情况下,场景一50万文件没有OOM,当把写文件数提高到了500万,就出现了OOM。说明不断增加文件数量,非java进程的内存一直在增加。

场景三:写入50万文件,然后再删除50万文件。堆内存:1G,容器最大内存:1500M WSS和RSS差值先涨到299M,文件删除后,内存差值降到3M 堆内存:1G,非堆:50M左右 对比删除文件前和删除文件后的memory.stat文件。可以看到文件删除前slab_reclaimable的大小和(WSS-RSS)的差值基本一致,删除文件后slab_reclaimable也下降,下降到2M左右。

总结

结合第三个场景的数据,基本可以确认,容器的非RSS内存增加和文件创建的文件数量有关系。创建文件产生的内存则是slab_reclaimable。 再结合场景二的情况,说明slab_reclaimable在容器中无法被回收,从而导致了OOM。 用相同代码直接在虚拟机上运行,限制内存大小,发现是slab_reclaimable可以被回收的,slab_reclaimable大小会维持在50M左右,不会不断增长。而在容器中,slab_reclaimable无法回收,会一直增长最终导致容器被杀。

参考资料: 【Linux】Linux内核空间的slab分配模式-CSDN博客

相关推荐
小王努力学编程2 小时前
从零开始的 Docker 之旅
linux·运维·服务器·docker·容器·容器编排·镜像制作
007php0079 小时前
使用 Docker、Jenkins、Harbor 和 GitLab 构建 CI/CD 流水线
数据库·ci/cd·docker·容器·golang·gitlab·jenkins
退役小学生呀13 小时前
十九、云原生分布式存储 CubeFS
分布式·docker·云原生·容器·kubernetes·k8s
deeper_wind17 小时前
k8s-容器化部署论坛和商城服务(小白的“升级打怪”成长之路)
linux·运维·容器·kubernetes
wdxylb19 小时前
云原生俱乐部-k8s知识点归纳(8)
云原生·容器·kubernetes
hello_ world.21 小时前
k8s笔记04-常用部署命令
笔记·容器·kubernetes
Monly211 天前
Docker:部署Nginx
nginx·docker·容器
会飞的土拨鼠呀1 天前
K8s部署MySQL8.0数据库
数据库·容器·kubernetes
Monly211 天前
Docker:技巧汇总
运维·docker·容器
Aurora1 天前
云原生---企业级Kubernetes
云原生·容器·kubernetes