一 背景
一台线上机器内存触达告警水位,业务方通过/proc/meminfo中发现大部分内存消耗在Cached字段,如下所示:
...... Cached: 101182324 kB ...... Active(file): 10444176 kB Inactive(file): 41598032 kB ......
从上面的信息可以看到Cached占用达到100GB,以往的经验来看page cache的主要贡献者是file page;但是这里Active(file)+Inactive(file)合起来总共也才50GB左右,远未达到page cahe的100GB。这大概50GB左右的Cache是被谁占用了呢?
二 消失的成分分析
为了能够找到这个消失的cache内存,我默默打开了内核源码,查看它是如何统计的:
void si_meminfo(struct sysinfo *val) { val->totalram = totalram_pages; val->sharedram = global_node_page_state(NR_SHMEM); val->freeram = global_zone_page_state(NR_FREE_PAGES); val->bufferram = nr_blockdev_pages(); val->totalhigh = totalhigh_pages; val->freehigh = nr_free_highpages(); val->mem_unit = PAGE_SIZE; } /* 获取系统内存信息到i */ si_meminfo(&i); /* cached值等于(vm_node_stat[NR_FILE_PAGES] - swapcache - buffer)*PAGE_SIZE */ cached = global_node_page_state(NR_FILE_PAGES) - total_swapcache_pages() - i.bufferram;
- global_node_page_state[NR_FILE_PAGES]:实际上是vm_node_stat[NR_FILE_PAGES]的page数量;通过内核源码了解到shmem、swap、shmem_thp、file page、block page cache都会统计到vm_node_stat[NR_FILE_PAGES]上。
- total_swapcache_pages():来自于spaces = swapper_spaces[i]中的所spaces[j].nrpages之和 。
- i.bufferram:表示块设备的page cache。
因此总体而言,/proc/meminfo中的"Cached"字段包含了file page cache、shmem、shmem_thp。
因此上面的案例中,消失的cache很有可能就是shmem或者shmem_thp。
三 啊,是共享内存
有了前面的分析,再次去/proc/meminfo中确认,确认的结果中"Shmem:"字段正好有50GB左右,这下算是实锤了是共享内存的锅。
可是随之而来的是,这50GB的shmem是谁消耗的呢?
要了解shmem究竟是谁消耗的,首先需要了解shmem由哪些机制创建:
一是通过一些接口创建到共享内存,如SYS V共享内存、POSIX共享内存、memfd_create;此外在rootfs,tmpfs,devtmpfs这些文件系统下创建的文件也会统计到shmem中。这些shmem不同的使用方式可以通过一些方法统计出来。其实大部分的shmem都是通过tmpfs机制来实现的。
ls -l /proc/sysvrpc/shm #sys v共享内存 ls -l /dev/shm #shmem du /dev/ #注解:这个是(devtmpfs) du检查所有的tmpfs挂载点
出乎意料的是从上面几个可能的shmem消耗源并未凑齐消失的50GB shmem。
就在焦头烂额之际,我突然想到之前遇到的一个问题,既然shmem都是以tmpfs文件的方式来实现,那么有可能这些文件被打开、未关闭的情况下删除掉,那么du方式就统计不到了。
为此,我们在挂载tmpfs的目录/run下执行"losf -a +L1 /<mountPoint>",即"lsof -a +L1 /run/"
通过这个命令果然检查到有两个java进程使用memfd分配了接近50GB的shmem,而且这两个memfd句柄都在未closed的情况下就被删除了。至此终于真相大白,那个shmem就是两个java进程通过memfd分配的,由于在未closed的情况下被删除,因此普通的du命令就统计不到。
