温暖的电波  

一 背景

    一台线上机器内存触达告警水位,业务方通过/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命令就统计不到。

posted on 2023-11-07 21:32  温暖的电波  阅读(390)  评论(0编辑  收藏  举报