一、背景
近期在公司的某台linux虚拟机上,发现内存几乎消耗殆尽,但找不到其去向。
在调查过程中,重点分析了/proc/meminfo文件,对其内存占用进行了学习与分析。
特记录在此,与君分享。
二、环境
- 虚拟机OS : CentOS Linux release 7.4.1708 (Core)
- 虚拟机平台 : VMWare
三、问题描述
通过free -h
或top
查看内存消耗,发现used已接近最大可用内存,但各进程常驻内存(RES)远比used要小。
先摆出结论:在VMWare虚拟机平台上,宿主机可以通过一个叫Balloon driver(vmware_balloon module)的驱动程序模拟客户机linux内部的进程占用内存,被占用的内存实际上是被宿主机调度到其他客户机去了。
但这种驱动程序模拟的客户机进程在linux上的内存动态分配并没有被linux内核统计进来,于是造成了上述问题的现象。
3.1 top 结果
按内存消耗排序,取消耗大于0的部分
top - 16:46:45 up 8 days, 10:25, 1 user, load average: 0.00, 0.01, 0.05
Tasks: 109 total, 1 running, 108 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 7994080 total, 185776 free, 7625996 used, 182308 buff/cache
KiB Swap: 4157436 total, 294944 free, 3862492 used. 115964 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND DATA
3725 root 20 0 9057140 1.734g 5020 S 0.3 22.7 367:48.86 java 8882680
1087 mysql 20 0 2672240 233064 1076 S 0.0 2.9 102:33.71 mysqld 2596840
496 root 20 0 36828 3512 3388 S 0.0 0.0 0:31.13 systemd-journal 356
14564 root 20 0 145700 2424 1148 S 0.0 0.0 0:02.94 sshd 924
1 root 20 0 128164 2404 724 S 0.0 0.0 1:02.18 systemd 84628
14713 root 20 0 157716 2204 1512 R 0.0 0.0 0:00.08 top 1176
14568 root 20 0 115524 1784 1272 S 0.0 0.0 0:00.59 bash 632
687 root 20 0 305408 1548 1168 S 0.0 0.0 13:59.34 vmtoolsd 75352
676 root 20 0 216388 1240 872 S 0.0 0.0 1:56.69 rsyslogd 148768
682 root 20 0 472296 908 160 S 0.0 0.0 1:06.73 NetworkManager 222852
684 root 20 0 24336 752 444 S 0.0 0.0 0:22.19 systemd-logind 504
690 polkitd 20 0 534132 560 220 S 0.0 0.0 0:07.34 polkitd 450080
677 dbus 20 0 32772 460 128 S 0.0 0.0 0:08.34 dbus-daemon 8900
688 root 20 0 21620 452 296 S 0.0 0.0 4:42.68 irqbalance 488
698 root 20 0 126232 432 328 S 0.0 0.0 0:30.25 crond 1312
922 root 20 0 562392 412 28 S 0.0 0.0 4:52.69 tuned 304472
924 root 20 0 105996 188 92 S 0.0 0.0 0:03.64 sshd 760
653 root 16 -4 55452 84 0 S 0.0 0.0 0:08.81 auditd 8664
532 root 20 0 46684 4 4 S 0.0 0.0 0:02.81 systemd-udevd 1916
705 root 20 0 110044 4 4 S 0.0 0.0 0:00.02 agetty 344
3.2 top结果第四行内存总体使用情况
属性 | 大小(G) | 说明 |
---|---|---|
total | 7.6 | 可分配内存总计值 |
used | 7.26 | 已分配内存 |
free | 0.17 | 未分配内存 |
buff/cache | 0.17 | buff与缓存 |
- 各属性满足公式:total = used + free + buff/cache
- used在该linux版本(centos7)上,已经反映实际分配的内存,不需要再去除buff/cache部分
3.3 top进程列表内存相关列统计
列 | 合计(G) | 说明 |
---|---|---|
VIRT | 14.236 | 进程申请的虚拟内存大小,申请不意味着分配,该值与实际内存消耗关系不大。 |
RES | 1.9747 | 进程常驻内存,包含进程间共享内存。 |
SHR | 0.0171 | 进程间共享内存,该值是推算出来的,存在误差,意义不大。 |
3.4 问题来了
RES合计值比used少了5G多!这些内存哪去了?
理论上,各进程的RES合计值因为会重复计算共享内存,应该比used值略大。实际上这两个值也往往是接近的,不应该差这么多。
四、清查linux内存消耗
为了进一步检查linux中内存消耗的去向,需要对/proc/meminfo
文件进行一次彻底的分析统计。
linux上各种内存查看工具如free,top实际上都是从/proc下面找linux内核的各种统计文件。
4.1 /proc/meminfo内容
MemTotal: 7994080 kB
MemFree: 125256 kB
MemAvailable: 932412 kB
Buffers: 8 kB
Cached: 993796 kB
SwapCached: 252 kB
Active: 1182220 kB
Inactive: 1213960 kB
Active(anon): 693796 kB
Inactive(anon): 717156 kB
Active(file): 488424 kB
Inactive(file): 496804 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 4157436 kB
SwapFree: 4157172 kB
Dirty: 8 kB
Writeback: 0 kB
AnonPages: 1402140 kB
Mapped: 41584 kB
Shmem: 8576 kB
Slab: 143220 kB
SReclaimable: 86720 kB
SUnreclaim: 56500 kB
KernelStack: 5360 kB
PageTables: 7184 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 8154476 kB
Committed_AS: 2073776 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 191584 kB
VmallocChunk: 34359310332 kB
HardwareCorrupted: 0 kB
AnonHugePages: 1284096 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 89920 kB
DirectMap2M: 4104192 kB
DirectMap1G: 6291456 kB
4.2 meminfo内容分析
属性 | 大小(k) | 说明 | 扩展说明 |
---|---|---|---|
MemTotal: | 7994080 | 可供linux内核分配的内存总量。 | 比物理内存总量少一点,因为主板/固件会保留一部分内存、linux内核自己也会占用一部分内存。 |
MemFree: | 125256 | 表示系统尚未分配的内存。 | |
MemAvailable: | 932412 | 当前可用内存。 | MemFree只是尚未分配的内存,并不是所有可用的内存。有些已经分配掉的内存是可以回收再分配的。比如cache/buffer、slab都有一部分是可以回收的,这部分可回收的内存加上MemFree才是系统可用的内存,即MemAvailable。同时要注意,MemAvailable是内核使用特定的算法估算出来的,并不精确。 |
Buffers: | 8 | 块设备(block device)所占用的特殊file-backed pages,包括:直接读写块设备,以及文件系统元数据(metadata)比如superblock使用的缓存页。 | Buffers内存页同时也在LRU list中,被统计在Active(file)或Inactive(file)之中。 |
Cached: | 993796 | 所有file-backed pages | 用户进程的内存页分为两种:file-backed pages(与文件对应的内存页),和anonymous pages(匿名页),比如进程的代码、映射的文件都是file-backed,而进程的堆、栈都是不与文件相对应的、就属于匿名页。file-backed pages在内存不足的时候可以直接写回对应的硬盘文件里,称为page-out,不需要用到交换区(swap);而anonymous pages在内存不足时就只能写到硬盘上的交换区(swap)里,称为swap-out。 |
SwapCached: | 252 | SwapCached包含的是被确定要swap-out,但是尚未写入交换区的匿名内存页。 | SwapCached内存页会同时被统计在LRU或AnonPages或Shmem中,它本身并不占用额外的内存。 |
Active: | 1182220 | active包含active anon和active file | LRU是一种内存页回收算法,Least Recently Used,最近最少使用。LRU认为,在最近时间段内被访问的数据在以后被再次访问的概率,要高于最近一直没被访问的页面。于是近期未被访问到的页面就成为了页面回收的第一选择。Linux kernel会记录每个页面的近期访问次数,然后设计了两种LRU list: active list 和 inactive list, 刚访问过的页面放进active list,长时间未访问过的页面放进inactive list,回收内存页时,直接找inactive list即可。另外,内核线程kswapd会周期性地把active list中符合条件的页面移到inactive list中。 |
Inactive: | 1213960 | inactive包含inactive anon和inactive file | |
Active(anon): | 693796 | 活跃匿名页,anonymous pages(匿名页)。 | |
Inactive(anon): | 717156 | 非活跃匿名页 | |
Active(file): | 488424 | 活跃文件内存页 | |
Inactive(file): | 496804 | 非活跃文件内存页 | |
Unevictable: | 0 | 因为种种原因无法回收(page-out)或者交换到swap(swap-out)的内存页 | Unevictable LRU list上是不能pageout/swapout的内存页,包括VM_LOCKED的内存页、SHM_LOCK的共享内存页(同时被统计在Mlocked中)、和ramfs。在unevictable list出现之前,这些内存页都在Active/Inactive lists上,vmscan每次都要扫过它们,但是又不能把它们pageout/swapout,这在大内存的系统上会严重影响性能,unevictable list的初衷就是避免这种情况的发生。 |
Mlocked: | 0 | 被系统调用"mlock()"锁定到内存中的页面。Mlocked页面是不可收回的。 | 被锁定的内存因为不能pageout/swapout,会从Active/Inactive LRU list移到Unevictable LRU list上。Mlocked与以下统计项重叠:LRU Unevictable,AnonPages,Shmem,Mapped等。 |
SwapTotal: | 4157436 | swap空间总计 | |
SwapFree: | 4157172 | 当前剩余swap | |
Dirty: | 8 | 需要写入磁盘的内存页的大小 | Dirty并不包括系统中全部的dirty pages,需要再加上另外两项:NFS_Unstable 和 Writeback,NFS_Unstable是发给NFS server但尚未写入硬盘的缓存页,Writeback是正准备回写硬盘的缓存页。 |
Writeback: | 0 | 正在被写回的内存页的大小 | |
AnonPages: | 1402140 | Anonymous pages(匿名页)数量 + AnonHugePages(透明大页)数量 | 进程所占的内存页分为anonymous pages和file-backed pages,理论上,所有进程的PSS之和 = Mapped + AnonPages 。PSS是Proportional Set Size,每个进程实际使用的物理内存(比例分配共享库占用的内存),可以在/proc/[1-9]*/smaps 中查看。 |
Mapped: | 41584 | 正被用户进程关联的file-backed pages | Cached包含了所有file-backed pages,其中有些文件当前不在使用,但Cached仍然可能保留着它们的file-backed pages;而另一些文件正被用户进程关联,比如shared libraries、可执行程序的文件、mmap的文件等,这些文件的缓存页就称为mapped。 |
Shmem: | 8576 | Shmem统计的内容包括:1.shared memory;2.tmpfs和devtmpfs。所有tmpfs类型的文件系统占用的空间都计入共享内存,devtmpfs是/dev文件系统的类型,/dev/下所有的文件占用的空间也属于共享内存。可以用ls和du命令查看。如果文件在没有关闭的情况下被删除,空间仍然不会释放,shmem不会减小,可以用 lsof -a +L1 /<mount_point> 命令列出这样的文件。 |
shared memory被视为基于tmpfs文件系统的内存页,既然基于文件系统,就不算匿名页,所以不被计入/proc/meminfo中的AnonPages,而是被统计进了:Cached 或Mapped (当shmem被attached时候)。然而它们背后并不存在真正的硬盘文件,一旦内存不足的时候,它们是需要交换区才能swap-out的,所以在LRU lists里,它们被放在Inactive(anon) 或 Active(anon) 或 unevictable (如果被locked的话)里。注意:/proc/meminfo中的 Shmem 统计的是已经分配的大小,而不是创建时申请的大小。 |
Slab: | 143220 | 通过slab分配的内存,Slab=SReclaimable+SUnreclaim | slab是linux内核的一种内存分配器。linux内核的动态内存分配有以下几种方式:1.alloc_pages/__get_free_page :以页为单位分配。2.vmalloc :以字节为单位分配虚拟地址连续的内存块。3.slab :对小对象进行分配,不用为每个小对象分配一个页,节省了空间;内核中一些小对象创建析构很频繁,Slab对这些小对象做缓存,可以重复利用一些相同的对象,减少内存分配次数。4.kmalloc :以slab为基础,以字节为单位分配物理地址连续的内存块。 |
SReclaimable: | 86720 | slab中可回收的部分。 | |
SUnreclaim: | 56500 | slab中不可回收的部分。 | |
KernelStack: | 5360 | 给用户线程分配的内核栈消耗的内存页 | 每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或异常(exception)进入内核态的时候才会用到,也就是说内核栈是给kernel code使用的。在x86系统上Linux的内核栈大小是固定的8K或16K。Kernel stack(内核栈)是常驻内存的,既不包括在LRU lists里,也不包括在进程的RSS/PSS内存里。RSS是Resident Set Size 实际使用物理内存(包含共享库占用的内存),可以在/proc/[1-9]*/smaps 中查看。 |
PageTables: | 7184 | Page Table的消耗的内存页 | Page Table的用途是翻译虚拟地址和物理地址,它是会动态变化的,要从MemTotal中消耗内存。 |
NFS_Unstable: | 0 | 发给NFS server但尚未写入硬盘的缓存页 | |
Bounce: | 0 | bounce buffering消耗的内存页 | 有些老设备只能访问低端内存,比如16M以下的内存,当应用程序发出一个I/O 请求,DMA的目的地址却是高端内存时(比如在16M以上),内核将在低端内存中分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处。 |
WritebackTmp: | 0 | 正准备回写硬盘的缓存页 | |
CommitLimit: | 8154476 | overcommit阈值,CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap | Linux是允许memory overcommit的,即承诺给进程的内存大小超过了实际可用的内存。commit(或overcommit)针对的是内存申请,内存申请不等于内存分配,内存只在实际用到的时候才分配。但可以申请的内存有个上限阈值,即CommitLimit,超出以后就不能再申请了。 |
Committed_AS: | 2073776 | 所有进程已经申请的内存总大小 | |
VmallocTotal: | 34359738367 | 可分配的虚拟内存总计 | |
VmallocUsed: | 191584 | 已通过vmalloc分配的内存,不止包括了分配的物理内存,还统计了VM_IOREMAP、VM_MAP等操作的值 | VM_IOREMAP是把IO地址映射到内核空间、并未消耗物理内存 |
VmallocChunk: | 34359310332 | 通过vmalloc可分配的虚拟地址连续的最大内存 | |
HardwareCorrupted: | 0 | 因为内存的硬件故障而删除的内存页 | |
AnonHugePages: | 1284096 | AnonHugePages统计的是Transparent HugePages (THP),THP与Hugepages不是一回事,区别很大。Hugepages在/proc/meminfo中是被独立统计的,与其它统计项不重叠,既不计入进程的RSS/PSS中,又不计入LRU Active/Inactive,也不会计入cache/buffer。如果进程使用了Hugepages,它的RSS/PSS不会增加。而AnonHugePages完全不同,它与/proc/meminfo的其他统计项有重叠,首先它被包含在AnonPages之中,而且在/proc/<pid>/smaps中也有单个进程的统计,与进程的RSS/PSS是有重叠的,如果用户进程用到了THP,进程的RSS/PSS也会相应增加,这与Hugepages是不同的。 | Transparent Huge Pages 缩写 THP ,这个是 RHEL 6 开始引入的一个功能,在 Linux6 上透明大页是默认启用的。由于 Huge pages 很难手动管理,而且通常需要对代码进行重大的更改才能有效的使用,因此 RHEL 6 开始引入了 Transparent Huge Pages ( THP ), THP 是一个抽象层,能够自动创建、管理和使用传统大页。THP 为系统管理员和开发人员减少了很多使用传统大页的复杂性 , 因为 THP 的目标是改进性能 , 因此其它开发人员 ( 来自社区和红帽 ) 已在各种系统、配置、应用程序和负载中对 THP 进行了测试和优化。这样可让 THP 的默认设置改进大多数系统配置性能。但是 , 不建议对数据库工作负载使用 THP 。这两者最大的区别在于 : 标准大页管理是预分配的方式,而透明大页管理则是动态分配的方式。 |
HugePages_Total: | 0 | 预分配的可使用的标准大页池的大小。HugePages在内核中独立管理,只要一经定义,无论是否被使用,都不再属于free memory。 | Huge pages(标准大页) 是从 Linux Kernel 2.6 后被引入的,目的是通过使用大页内存来取代传统的 4kb 内存页面, 以适应越来越大的系统内存,让操作系统可以支持现代硬件架构的大页面容量功能。 |
HugePages_Free: | 0 | 标准大页池中尚未分配的标准大页 | |
HugePages_Rsvd: | 0 | 用户程序预申请的标准大页,尚未真的分配走 | |
HugePages_Surp: | 0 | 标准大页池的盈余 | |
Hugepagesize: | 2048 | 标准大页大小,这里是2M | |
DirectMap4k: | 89920 | 映射为4kB的内存数量 | DirectMap所统计的不是关于内存的使用,而是一个反映TLB效率的指标。TLB(Translation Lookaside Buffer)是位于CPU上的缓存,用于将内存的虚拟地址翻译成物理地址,由于TLB的大小有限,不能缓存的地址就需要访问内存里的page table来进行翻译,速度慢很多。为了尽可能地将地址放进TLB缓存,新的CPU硬件支持比4k更大的页面从而达到减少地址数量的目的, 比如2MB,4MB,甚至1GB的内存页,视不同的硬件而定。所以DirectMap其实是一个反映TLB效率的指标。 |
DirectMap2M: | 4104192 | 映射为2MB的内存数量 | |
DirectMap1G: | 6291456 | 映射为1GB的内存数量 |
4.3 根据meminfo统计内存占用
linux上的内存消耗总的来说有两部分,一部分是内核kernel进程,另一部分是用户进程。因此我们统计这两部分进程的内存消耗再相加即可。
- 内核部分:
Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X
X是指linux内核没有统计进来的,动态内存分配中通过alloc_pages分配的内存。
http://linuxperf.com/?cat=7
就指出了一个这样的例子:在VMware guest上有一个常见问题,就是VMWare ESX宿主机会通过guest上的Balloon driver(vmware_balloon module)占用guest的内存,有时占用得太多会导致guest无内存可用,这时去检查guest的/proc/meminfo只看见MemFree很少、但看不出内存的去向,原因就是Balloon driver通过alloc_pages分配内存,没有在/proc/meminfo中留下统计值,所以很难追踪。
- 用户进程部分:
Active + Inactive + Unevictable + (HugePages_Total * Hugepagesize)
或Cached + AnonPages + Buffers + (HugePages_Total * Hugepagesize)
根据上述公式,除掉X部分,得出linux内核统计出来的已分配内存为:2.62G。
该值远小于使用free
或top
得到的used。
这里推测原因就是linux没有统计进来的alloc_pages分配的内存。
考虑到该linux确实是在VMWare平台上申请的虚拟机,因此我们推测是由于虚拟机平台内存不足,于是宿主机模拟该客户机内部进程消耗内存,实际上将内存调度到其他虚客户机去了。
五、结论
虚拟机管理员尚未最终确认,目前仅仅是推测
在VMWare虚拟机平台上,宿主机可以通过一个叫Balloon driver(vmware_balloon module)的驱动程序模拟客户机linux内部的进程占用内存,被占用的内存实际上是被宿主机调度到其他客户机去了。
但这种驱动程序模拟的客户机进程在客户机linux上是通过alloc_pages实现的内存动态分配,并没有被linux内核统计进来,于是造成了内存去向不明的现象。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。