香港服务器可用内存为何越来越少:如何区分缓存增长与进程泄漏

先判断“可用内存变少”是否等于内存泄漏
香港服务器运行 Linux 时,系统会主动把暂时空闲的内存用于文件缓存、目录索引和内核可回收对象。因此,free 变小、used 变大,并不能直接证明某个进程泄漏内存。
真正需要先确认的是:MemAvailable 是否持续下降,交换分区是否发生持续读写,进程的常驻内存是否随时间单调增长,以及是否出现 OOM(Out of Memory)记录。简单地说:
free少、buff/cache多、MemAvailable仍有余量,通常是缓存利用,不一定是故障。MemAvailable持续下降,某个进程的RSS、PSS或匿名内存同步增长,更接近进程占用异常。Swap已使用不等于当前正在发生内存压力,还要结合vmstat中的换入换出活动判断。- OOM 可能发生在整台服务器,也可能只发生在某个容器或 systemd cgroup 内,不能只看主机总内存。
先在业务负载相对稳定时执行以下只读命令,建立一组基线:
date
uptime
free -h
grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SReclaimable|SUnreclaim|Shmem|AnonPages|SwapTotal|SwapFree' /proc/meminfo
vmstat 1 5
ps -eo pid,ppid,comm,rss,vsz,%mem --sort=-rss | head -n 15
vmstat 1 5 的第一行通常是启动以来的平均值,判断当前压力时应重点观察后续按秒输出的行。
这些指标分别说明什么
MemFree 不是最重要的可用内存指标
Linux 会尽量避免内存闲置,所以 MemFree 很低并不必然异常。判断系统还能否较平稳地承受新负载,应优先看:
MemAvailable:内核根据当前可回收页、空闲页和内存水位估算出的可用内存。Swap活动:是否持续发生页面换入和换出。- 内存压力和业务延迟:是否因为回收、换页而出现等待。
- OOM 事件:是否已经无法满足分配请求。
MemAvailable 是估算值,不是保证可以完整分配给单个进程的连续内存,也不代表所有内存都能立即回收。内核版本、文件缓存、内存碎片和大页使用情况都会影响它。
常见指标的判断方式
| 指标 | 主要含义 | 能支持的判断 | 不能单独证明的事情 |
|---|---|---|---|
MemFree | 当前完全空闲的内存 | 空闲页数量 | 不能代表服务器实际还能使用的全部内存 |
MemAvailable | 内核估算的可用内存 | 判断整体余量是否持续下降 | 不能保证任意一次大内存分配一定成功 |
Cached | 文件页缓存等可回收页面 | 判断文件访问是否产生较多缓存 | 不能说明缓存一定能在瞬间全部释放 |
SReclaimable | 一部分可回收内核 slab | 判断目录、inode 等内核对象是否占用较多空间 | 不能把所有 slab 都视为立即可回收 |
SwapFree 或 SwapUsed | 交换空间的占用情况 | 判断是否有页面曾被移出物理内存 | 不能单独证明当前正在发生严重内存压力 |
vmstat 的 si、so | 每个采样周期的换入、换出活动 | 判断当前是否持续换页 | 一次短暂的非零值不能证明进程泄漏 |
进程 RSS | 进程当前驻留在物理内存中的页面 | 找出占用较大的进程 | 共享页面可能被多个进程重复计入 |
进程 PSS | 按比例分摊共享页面后的占用 | 更合理地比较多个进程的实际内存归属 | 仍不能单独判断“有意缓存”还是“泄漏” |
RssAnon | 进程的匿名常驻内存 | 观察堆、匿名映射等增长 | 增长也可能来自正常业务缓存或并发提升 |
| OOM 日志 | 内核或 cgroup 曾触发内存不足处理 | 确认发生过 OOM 处置 | 没有日志不代表一定没有压力,日志可能已轮转或权限不足 |
free -h 中的 buff/cache 通常包含文件缓存、buffer 和一部分可回收对象。它较高时,若 MemAvailable 稳定、si/so 基本没有持续活动,通常不应仅凭这个数字清理缓存或重启服务。
如何确认是正常缓存增长
典型特征
正常的内核缓存通常具有以下表现:
- 服务器读取大量文件后,
Cached或相关可回收项目增加。 - 业务停止或访问模式改变后,缓存增长趋于平稳。
- 发生真实内存需求时,内核可以回收其中一部分页面。
MemAvailable没有随着缓存增加而持续快速下降。vmstat中没有持续的si、so,业务延迟也没有明显因换页升高。
需要注意,应用自身的缓存不一定出现在 Cached 中。例如,应用将对象、结果集或会话数据保存在堆内存中时,它们通常属于匿名内存,会体现在进程的 RSS 或 PSS 中。此时不能因为它被称为“缓存”就认为它可以被内核自动回收。
可以用下面的命令观察系统级内存类别:
free -h
grep -E 'MemAvailable|Cached|Buffers|SReclaimable|SUnreclaim|AnonPages|Shmem|SwapFree' /proc/meminfo
swapon --show
如果 Cached 增长,但 MemAvailable 仍有相对稳定的余量,且换页活动很低,优先把它视为系统在利用内存提高文件访问效率。这个判断适用于负载和内存总量没有突然变化的时间段;如果同时出现业务延迟、MemAvailable 快速下降或 OOM,就不能只用“缓存正常”解释。
不要把清理缓存当成泄漏检测
生产环境中不应把手动清理 page cache 作为常规排查手段。清理缓存可能造成后续文件读取重新访问存储,短时间内增加 I/O 和延迟,而且即使清理后内存数字变好,也不能证明之前存在进程泄漏。
如果确实需要验证缓存回收能力,应优先在测试环境或可接受影响的维护窗口中进行,并记录操作前后的:
MemAvailableCached、SReclaimablevmstat的si/so- 业务延迟和 I/O 等待
- 主要进程的
RSS、PSS
在生产香港服务器上,更安全的做法是观察自然负载变化,或使用与生产相近的受控压力测试,而不是直接制造内存压力。
如何确认是进程占用增长或疑似泄漏
先看趋势,不要只看一次排名
ps 排名只能告诉你当前谁占用较多,不能告诉你谁在泄漏。应在相同或相近业务负载下连续采样,关注同一个 PID 的变化。
ps -eo pid,ppid,comm,rss,vsz,%mem --sort=-rss | head -n 20
其中:
RSS通常以 KiB 显示,是当前驻留内存。VSZ是虚拟地址空间大小,包含尚未实际占用物理内存的区域,不适合直接当作内存消耗。%MEM是相对整机物理内存的比例,容器或 cgroup 场景下不能直接代表该服务相对自身限制的比例。
对目标进程进一步检查:
PID=1234
if [ -r "/proc/$PID/smaps_rollup" ]; then
cat "/proc/$PID/smaps_rollup"
else
grep -E 'VmRSS|VmSwap|RssAnon|RssFile|RssShmem' "/proc/$PID/status"
fi
smaps_rollup 并非所有内核环境都提供,因此命令先检查文件是否存在。重点观察:
Pss:按比例计算共享页后的总占用。Rss:进程当前驻留内存。Private_Clean、Private_Dirty:更接近进程独占的页面。Swap:该进程有多少页面已经进入交换空间。/proc/PID/status中的RssAnon:匿名内存占用。RssFile:文件映射或文件相关页面。
具备哪些条件才接近“泄漏”
单次看到某进程内存较高,不足以判定泄漏。更有价值的证据是:
- 在业务并发和请求类型大致相同的情况下,进程
PSS或RssAnon持续上升。 - 一个批次、请求或任务结束后,内存不再回落,且重复执行相同工作后继续抬高。
- 增长主要来自匿名私有内存,而不是可以回收的文件页面。
- 进程增长与整机
MemAvailable下降、Swap 活动或 cgroup 限制触发同步出现。 - 对象数量、连接数、线程数、队列长度或应用内部缓存统计也出现同方向增长。
可以用简单的循环记录趋势。这个命令只读取 /proc,不会终止进程:
PID=1234
while [ -r "/proc/$PID/status" ]; do
printf '%s ' "$(date '+%F %T')"
awk '/^(VmRSS|VmSwap|RssAnon|RssFile|RssShmem):/ {printf "%s=%s%s ", $1, $2, $3}' "/proc/$PID/status"
printf '\n'
sleep 60
done
采样时应同时记录业务量,否则容易把正常扩容误判为泄漏。例如,请求并发增加、批处理规模变大、连接池扩大、应用缓存预热,都可能使内存上升。只有在负载回到相近水平后仍不回收,泄漏嫌疑才更强。
如果系统安装了 sysstat,也可以使用 pidstat 查看进程内存变化:
pidstat -r -p 1234 60
该命令是否可用取决于发行版是否安装了 sysstat。它提供的是采样数据,不会解释增长原因,仍需结合进程自身的堆、缓存和连接指标。
RSS 增长也不一定就是代码泄漏
以下情况可能造成内存长期保持较高,但未必是传统意义上的泄漏:
- 内存分配器为了后续复用,暂时不把已释放的堆空间归还给内核。
- 垃圾回收器已经释放对象,但运行时保留了堆容量。
- 线程、连接池或工作队列按峰值扩容后没有及时缩回。
- 共享库、共享内存和文件映射的统计方式造成误判。
- 应用缓存有明确上限,但业务规模正在增长。
- 大页、内存映射文件或临时文件映射改变了内存分布。
因此,重启服务后内存下降只能说明该进程原先持有的内存被释放了,不能单独证明存在泄漏。若重启后在相同负载下再次按近似速度增长,应继续收集应用层内存统计或堆分析数据。
交换空间和 OOM 应该怎样解读
Swap 已使用不等于正在发生故障
Linux 可能把一段时间不活跃的匿名页面放入 Swap,为文件缓存或当前活跃页面保留物理内存。因此,SwapUsed 有数值时,服务器仍可能运行正常。
判断当前是否存在交换压力,应结合:
vmstat 1 10
重点观察连续采样中的:
si:从交换空间读入内存的活动。so:从内存写入交换空间的活动。wa:I/O 等待变化。r、b:运行队列和阻塞任务变化。
如果 Swap 已经使用,但 si、so 长时间接近零,通常只能说明过去有页面被换出。若 si、so 持续升高,同时 MemAvailable 较低、业务响应变慢,就说明工作集已经超出当前可快速驻留的内存范围。此时应查清是进程增长、并发突增、cgroup 限制,还是内核内存占用增加。
检查系统级和服务级 OOM
Linux 内核日志可以帮助确认是否发生过 OOM:
journalctl -k -b | grep -Ei 'out of memory|oom-killer|killed process'
某些环境没有 journalctl,或者普通用户无法读取内核日志,可以在具备权限的前提下尝试:
dmesg | grep -Ei 'out of memory|oom-killer|killed process'
日志中如果出现被杀进程、触发原因和内存统计,说明内核已经执行了 OOM 处理。日志缺失可能是日志轮转、容器隔离、权限限制或记录位置不同造成的,不能把“没有搜到”当作“没有发生”。
还要确认服务是否运行在内存受限的 cgroup 中:
PID=1234
cat "/proc/$PID/cgroup"
if [ -f /sys/fs/cgroup/memory.current ]; then
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.events
fi
上述 memory.* 文件主要对应 cgroup v2,并且必须在目标服务所属的 cgroup 中读取。直接读取当前 Shell 所在的 cgroup,可能得到的是 Shell 的限制,而不是目标服务的限制。
memory.events 中的 oom 或 oom_kill 增加,说明该 cgroup 曾受到内存不足处置。即使主机还有可用内存,服务也可能因为自身的 memory.max 达到上限而被限制或杀死。容器环境同样需要区分容器限制和宿主机整体余量。
按低风险顺序排查内存异常
第一步:固定观察范围和负载
先记录服务器总内存、可用内存、Swap、主要进程和业务量。至少要知道异常发生时:
- 是整台香港服务器内存紧张,还是单个服务紧张。
- 请求量、并发数、批处理规模是否发生变化。
- 进程是否刚重启、刚完成配置变更或刚执行过缓存预热。
- 异常是持续增长,还是高峰过后能够回落。
没有相同负载下的对照,单纯比较两个时间点很容易得出错误结论。
第二步:区分文件缓存、内核占用和匿名内存
查看 /proc/meminfo 时,不要只看 Cached,还应同时比较:
AnonPages:用户进程匿名内存的大致规模。Cached:文件页缓存。SReclaimable:可回收 slab 的一部分。SUnreclaim:不容易直接回收的 slab。Shmem:共享内存、tmpfs 等相关占用。SwapFree:交换空间余量。
如果匿名内存随业务量持续增长,而文件缓存变化不大,应优先定位进程。若 SUnreclaim 快速增长,问题可能在内核对象、驱动、文件系统或某类内核资源,不能简单归咎于应用堆泄漏。
需要查看 slab 时,可以尝试:
slabtop -o
slabtop 不是所有系统默认安装,且不同发行版支持的选项可能不同。它适合辅助判断 slab 是否异常增长,不足以直接指认具体业务进程。
第三步:定位进程并记录 PSS 趋势
先用 RSS 找出候选进程,再用 smaps_rollup 或进程状态文件确认匿名、文件和共享内存的组成。对多个候选进程进行连续采样,而不是只跟踪当前排名第一的进程。
若某个进程的 RssAnon 和 PSS 持续增长,且负载回落后仍不下降,应查看该服务的:
- 应用缓存是否有上限。
- 连接、线程和队列是否持续累积。
- 垃圾回收或内存分配器是否存在延迟回收。
- 最近是否修改了批量大小、并发数或超时配置。
- 是否有未关闭的文件、连接、订阅或请求上下文。
这些检查需要结合具体运行时和应用日志完成,不能仅凭 Linux 进程表确定代码层面的泄漏位置。
第四步:验证修复,而不是只验证重启
如果必须重启服务作为临时止血措施,应先保留异常期间的指标、日志和进程信息,并确认重启会影响哪些请求、连接和任务。生产环境执行 systemctl restart、容器重建或进程终止前,应准备维护窗口、服务恢复方式和配置回滚方案;不要对无法确认归属的高占用 PID 直接使用 kill -9。
重启后的内存下降只能作为“释放成功”的验证。真正的修复验证应是:
- 在相近的业务负载下重新运行相同类型任务。
- 连续记录整机
MemAvailable、Swap 活动和目标进程PSS。 - 观察任务结束后内存是否回落到可接受的基线附近。
- 检查 OOM 日志、cgroup
memory.events和业务延迟是否不再增长。 - 若仍按类似速度增长,说明问题尚未解决,需继续检查应用缓存、分配器、运行时或输入规模。
如何作出处理边界
可以按以下条件区分“暂时不用处理”和“需要立即定位”:
| 观察结果 | 更可能的原因 | 处理方向 |
|---|---|---|
free 下降,但 MemAvailable 稳定,si/so 基本没有活动 | 文件缓存或可回收内存增加 | 继续观察,不要仅凭 free 清缓存 |
Cached 增长,负载结束后趋稳,业务延迟正常 | 文件访问形成 page cache | 以可用内存和延迟为准评估 |
某进程 RSS/PSS 随时间单调增长,RssAnon 同步增加 | 应用缓存、堆增长、运行时保留或疑似泄漏 | 固定负载复测并结合应用指标定位 |
Swap 已使用但 si/so 长期接近零 | 历史页面被换出,当前压力未必持续 | 结合 MemAvailable、延迟和后续趋势判断 |
si/so 持续活动且业务延迟升高 | 工作集超过可快速驻留内存 | 降低并发或任务规模,并定位占用来源 |
主机还有内存,但 cgroup memory.max 达到上限 | 服务级内存限制 | 检查限制配置与实际工作集,不能只看主机总量 |
出现 oom-kill 或 memory.events 中 oom_kill 增加 | 已发生内存不足处置 | 先保留证据,再修复增长来源或重新评估容量 |
容量判断不应使用一个脱离业务的固定百分比。更可靠的方法是,在可预测的峰值负载下测量进程实际工作集、内核开销、必要的文件缓存、并发突发和维护操作所需的余量。若只是缓存增加且 MemAvailable、换页活动和延迟保持稳定,通常不需要因为 free 变小就扩容;若匿名工作集持续增长、Swap 活动频繁、cgroup 反复触顶或已经出现 OOM,则应先修复占用异常,再根据复测峰值评估是否需要增加内存或调整服务并发。
对香港服务器进行最终判断时,至少应保留一段包含正常负载、峰值负载和负载回落的时间序列。只有在相同条件下复测后,才能区分一次性的缓存预热、正常工作集扩大与会反复增长的进程内存问题。