背景
近期遇到一个问题:一台 Linux 服务器告警(VMware 虚拟机):内存使用率长期偏高。登录后用 top 查看,used 已达 12 GB / 15.8 GB,但把进程列表里所有 RES 加起来不过 2~2.5 GB,剩下约 10 GB 内存"凭空消失"——从任何进程视角都找不到是谁占的(实则是 Linux 虚拟机专有的 气球内存 导致的),记录下问题定位过程。
解决
问题解决
问题描述
使用 top 命令看到 used 内存占用很高,但下面各个进程的内存占用很低:
MiB Mem : 15830.8 total, 730.1 free, 12174.0 used, 2926.7 buff/cache
进程 RES 求和 ≈ 2.5 GB,与 used 12 GB 存在 ~10 GB 缺口。top 默认只统计进程用户态驻留内存,下面几类内存不归任何进程:内核 Slab、共享内存/tmpfs、页表、HugePages、内核直接申请的裸页。
使用 free -h 同样可以看到内存占用很高。
关键验证:内存去向核算(/proc/meminfo)
查看内存占用情况:
cat /proc/meminfo | egrep 'MemTotal|MemFree|MemAvailable|Buffers|Cached|Shmem|Slab|SReclaimable|SUnreclaim|KernelStack|PageTables|VmallocUsed|HugePages_Total|AnonPages|Mapped'
AnonPages: 1328972 kB (~1.27 GB) ← 所有进程真正吃的匿名内存
Cached: 2695816 kB (~2.57 GB) ← 可回收文件缓存
Slab: 448552 kB (~0.43 GB)
PageTables: 22192 kB
VmallocUsed: 0 kB
HugePages_Total: 0
可核算合计仅 ≈ 5 GB,而 MemTotal 15.6 GB —— 缺口约 10 GB,所有标准字段都不认账。
尝试手动回收缓存占用的内存
# 手动回收缓存占用的内存
sync && echo 3 > /proc/sys/vm/drop_caches
| 指标 | 执行前 | 执行后 |
|---|---|---|
| used | 12179 | 11949(仅 ↓230 MB) |
| buff/cache | 2927 | 427 |
| free | 723 | 3454 |
缓存被成功回收,但 used 几乎不动 —— 证明这 10 GB 是不可回收的硬占用,不是缓存。
其实从 top 命令里也可以看出,这并不是 buff/cache 占用的内存,两者相差太多了。
确定问题:确认虚拟化 + 气球驱动
查看操作系统类型
systemd-detect-virt
输出:
vmware
确认是 VMware 虚拟化的操作系统(如果是其它类型的虚拟机也会有相应的输出),而非物理机(如果是物理机会输出 none)。
确认是否带有气球驱动
lsmod | egrep -i 'balloon|vmw|virtio'
输出:
vmw_vsock_virtio_transport_common 32768 1 vsock_loopback
vmw_vsock_vmci_transport 32768 1
vsock 45056 5 vmw_vsock_virtio_transport_common,vsock_loopback,vmw_vsock_vmci_transport
vmw_balloon 24576 0
vmw_vmci 86016 2 vmw_balloon,vmw_vsock_vmci_transport
vmwgfx 364544 2
drm_kms_helper 217088 1 vmwgfx
ttm 110592 1 vmwgfx
drm 557056 5 vmwgfx,drm_kms_helper,ttm
vmw_pvscsi 28672 4
确认存在气球驱动。
确认气球驱动占用的内存
vmware-toolbox-cmd stat balloon
输出:
10290 MB
至此,找到“丢失”的内存,的确是气球驱动占用了内存。
理论部分
什么是"吹气球"(Memory Ballooning)?
在虚拟化中,宿主机(ESXi)往往内存超分(overcommit)——分配给所有虚拟机的内存总和超过物理内存。当宿主机内存吃紧时,它需要从某些"用得不满"的 VM 里回收内存,分给更需要的 VM。
机制如下:每台装了 VMware Tools 的 guest 内都有一个气球驱动 vmw_balloon。宿主机命令它"充气",驱动就在 guest 内部调用 alloc_pages 申请大量物理页并锁住,再把这些页对应的物理内存交还给宿主机。
形象地说:
气球在 guest 里"占座",把座位(物理页)腾给宿主机去分配给别的 VM。guest 自己看:内存被"用掉"了;但用它的不是任何应用,而是这只"气球"。
这正是本例 10290 MB 的去向。
为什么从进程视角看不到占用?
气球占走的页是内核驱动直接申请的裸页:
- 不属于任何用户进程 →
top/ps任何进程都不背锅; - 不是匿名页(anon)、文件页(file)、也不走 slab →
/proc/meminfo、/proc/vmstat的标准字段都不计入; - 不可回收 →
drop_caches无效。
所以表现就是"used 很高,但谁都找不到"。
排查方向是如何确定的?
排查不是一上来就猜气球,而是逐层缩小范围:
-
先核算内存去向:把 meminfo 各项与
used对账,确认缺口确实存在且落在"非进程"区间。 -
判断可否回收:
drop_caches后used不降 → 排除文件缓存,锁定"硬占用"。 -
缩小到内核态:vmstat 所有计数器求和 < 1 GB → 缺口既不在用户态也不在常规内核统计里,只能是内核直接申请的裸页。
-
区分两大嫌疑(关键分叉):
- 内核模块泄漏(如安全 EDR agent 的 hook 模块通过
alloc_pages泄漏); - 气球驱动占用(虚拟机场景特有)。
两者现象完全相同,必须用
systemd-detect-virt+lsmod | egrep -i 'balloon|vmw|virtio'区分。本例命中 VMware + 活动的vmw_balloon,再用vmware-toolbox-cmd stat balloon拿到精确数值实锤。 - 内核模块泄漏(如安全 EDR agent 的 hook 模块通过
最终内存对账
| 项 | 数值 |
|---|---|
| 气球占用 (balloon) | 10,290 MB |
| 进程匿名内存 (anon) | ~354 MB |
| Slab + 页表 + kernel_misc | ~480 MB |
| 残余 buff/cache | ~427 MB |
| 合计 | ≈ 11.5 GB ≈ used 11949 MB ✅ |
账完全对上,排查闭环。
总结
-
根因:这台 VM 被 ESXi 宿主机通过
vmw_balloon回收(ballooning)了约 10.3 GB 内存,导致 guest 内used虚高、看似"内存丢失"。本机没有内存泄漏、没有问题进程,无需也无法自行修复。 -
本机不要做的事:
drop_caches、kill 进程、调内核参数均无效(气球页不可回收);重启 VM 只能暂时缓解,宿主机一压力气球会重新充起。 -
正确处置(交给虚拟化/平台团队):
- 检查 ESXi 宿主机内存使用与 balloon/swap 压力,确认是否超分;
- 给本 VM 配置内存预留(Memory Reservation),保护其不被吹气球;
- 给宿主机扩容物理内存,或用 vMotion 迁移部分 VM 分担压力。
-
长期监控:盯住一个值即可
vmware-toolbox-cmd stat balloon持续走高 = 宿主机内存压力加大(应用真要用内存而气球不放气时会触发 swap / 性能抖动);回落到接近 0 = 内存已自动还回。
-
方法论沉淀:遇到"
used高但进程找不到",按 meminfo 对账 → drop_caches 验回收 → vmstat 缩到内核态 → 区分泄漏/气球 的顺序排查;在虚拟机上,永远记得把 Ballooning 列为首要嫌疑之一。