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博客

相关推荐
Bright16685 小时前
centos9安装k8s集群
云原生·容器·kubernetes
!!!5256 小时前
华为云镜像加速器
docker·容器·华为云
xidianjiapei0017 小时前
Kubernetes的Ingress 资源是什么?
云原生·容器·kubernetes
sszdzq9 小时前
Docker
运维·docker·容器
dmy9 小时前
docker 快速构建开发环境
后端·docker·容器
土豆沒加10 小时前
K8S的Dashboard登录及验证
云原生·容器·kubernetes
终端行者12 小时前
kubernetes1.28部署mysql5.7主从同步,使用Nfs制作持久卷存储,适用于centos7/9操作系统,
数据库·容器·kubernetes
伪装成塔的小兵17 小时前
Windows使用docker部署fastgpt出现的一些问题
windows·docker·容器·oneapi·fastgpt
转身後 默落20 小时前
11.Docker 之分布式仓库 Harbor
分布式·docker·容器
菩提云21 小时前
Deepseek存算分离安全部署手册
人工智能·深度学习·安全·docker·容器