为什么服务显示占了 8G,实际内存却没那么多?

最近帮忙看一台测试机时,看到一个很吓人的告警:某个 Java 服务在 top 里显示占了 8G。机器总共才 16G 内存,大家第一反应都是"是不是要 OOM 了"。

但再看 RES,只有 900M 左右;free -h 也没有马上见底。第一次碰到这种数字打架,我也有点懵:同一个进程,怎么一会儿像 8G,一会儿又像不到 1G?

后来才知道,VIRT 和 RES 不是两种算错了的内存,而是在回答两件不同的事。这篇不讲虚拟内存原理课,只把排查里最容易误读的几个数字捋清楚。

一. 先看清楚,8G 出现在哪一列

  1. 很多误会都从截图开始。top 或 htop 里进程的内存列不止一个,最容易被放大的通常是 VIRT,有些工具也叫 VSZ。它表示进程已经拿到或映射到的虚拟地址空间,并不等于这些空间此刻都占着物理内存。

  2. RES 对应的则更接近当前驻留在内存里的页面。换句话说,进程可以先画出一大片"可用地址",但里面有不少页还没有真正被访问;它们不会因为出现在 VIRT 里,就立刻把机器内存吃掉。

  3. 所以看到一个很大的数字,先别急着重启服务。把同一个进程的几列一起拿出来看,至少能知道自己盯着的是地址空间,还是当前驻留量。

bash 复制代码
PID=12345
ps -p "$PID" -o pid,comm,vsz,rss,%mem
grep -E '^(VmSize|VmRSS|RssAnon|RssFile|RssShmem|VmSwap):' "/proc/$PID/status"
  1. Linux 的 /proc/PID/status 里,VmSize 对应虚拟内存大小,VmRSS 是当前驻留集;后者还能拆成匿名页、文件映射页和共享内存页。先把这层名字对齐,后面的排查才不会越看越慌。

二. 为什么服务会先申请一大片地址

  1. 以 Java 为例,堆的最大值、动态库、线程栈、内存映射文件,都会出现在进程的地址空间里。Node.js、数据库和一些高性能服务也会有类似情况:它们可能提前保留地址,或者把文件映射进来,等真正需要时再逐页使用。

  2. 这就像你在停车场里预留了 100 个车位,不代表现在真有 100 辆车停在那里。VIRT 更像"这片地方归我用",RES 才更接近"现在实际停了多少车"。所以单凭 VIRT 很大,不能推出服务真的泄漏了同样大的内存。

  3. 当然,RES 小也不代表完全没事。要是它持续上涨、机器的可用内存持续下降,或者已经开始频繁换页、使用 swap,那仍然需要继续查。关键是别用一个不合适的指标,提前给服务判死刑。

  4. 有时你还会发现两个进程都显示一大块 RES,它们却共享同一个动态库或文件页。把两份 RES 直接相加,可能又把共享部分重复算了一次。

三. 真要判断压力,别只盯着 VIRT 和 RES

  1. 如果告警来自"单进程到底该分多少内存",我会再看一次 PSS。它会把共享页面按参与共享的进程分摊,拿来估算一个进程对整机内存的实际份额通常更顺手。smaps_rollup 是把所有映射汇总后的版本,不用面对一长串地址段。
bash 复制代码
PID=12345
sudo grep -E '^(Rss|Pss|Pss_Anon|Pss_File|Pss_Shmem|Swap):' \
  "/proc/$PID/smaps_rollup"
  1. Linux Kernel 文档说明,smaps_rollup 会汇总各个映射的字段,并给出 Pss_Anon、Pss_File、Pss_Shmem。它很适合先判断"主要是匿名内存、文件映射还是共享内存",比只看到一个总数更容易找到下一步方向。官方说明在这里。

  2. 接着回到整机视角。进程没有真的把内存吃满,不代表机器没有压力:页缓存、其他服务、内核开销和容器限制都可能在起作用。MemAvailable、swap 活动和 vmstat 的走势,通常比某一刻的 VIRT 更能说明机器是否真的喘不过气。

bash 复制代码
free -h
vmstat 1 5
grep -E '^(MemAvailable|SwapTotal|SwapFree):' /proc/meminfo
  1. 这几组信息要放在同一个时间段看。一次性的 VmRSS 截图不够说明泄漏;连续几次采样中 PSS、RssAnon 和整机可用内存一起往坏的方向走,才更像是需要处理的增长。

四. 看到数字不对劲时,我会这样把问题缩小

  1. 先确认告警说的是哪个指标。是 APM 把 VIRT 当"内存使用量"报出来了,还是 RES/PSS 确实在涨?这一问经常能省掉半天错误方向的排查。

  2. 如果主要涨的是 RssAnon,就更关注应用自己分配、缓存和对象释放;如果是 Pss_File 或 RssFile,再去看最近是不是加载了大文件、索引或内存映射数据。它们不是结论,只是把"查内存"从一句大话变成更小的入口。

  3. 对线上服务,采样最好附带时间和进程启动时间,避免每次都拿不同代的进程对比。下面这个小命令会把最常用的几项留到一个文件里;目录请换成你们实际的临时或诊断目录。

bash 复制代码
PID=12345
OUT="/tmp/memory-$PID-$(date +%Y%m%d%H%M%S).txt"
{
  date
  ps -p "$PID" -o pid,lstart,etime,comm,vsz,rss,%mem
  grep -E '^(VmSize|VmRSS|RssAnon|RssFile|RssShmem|VmSwap):' "/proc/$PID/status"
  grep -E '^(Rss|Pss|Pss_Anon|Pss_File|Pss_Shmem|Swap):' "/proc/$PID/smaps_rollup"
} | sudo tee "$OUT"
  1. 这个命令可能因为权限、内核版本或进程已退出而读不到部分文件,别让它静默失败。生产环境也不要高频读取完整 smaps;官方文档提到它开销更高,优先取汇总的 smaps_rollup,并把频率控制在诊断需要的范围内。

五. 最后记住:大地址,不等于大压力

  1. VIRT/VSZ 大,首先说明进程的虚拟地址空间大;它不是"这台机器已经被这个进程占掉多少物理内存"的直接答案。RES/RSS 能补上驻留量,但遇到共享页时也别急着做简单相加。

  2. 真要估算单进程的份额,优先补看 PSS;真要判断机器是否有压力,就把 MemAvailable、swap 和一段时间内的趋势也拉进来。这样看到"服务显示 8G"时,至少知道该先问什么,而不是上来就改堆大小或重启。

  3. 如果最终确认的是应用真实增长,再回到对应语言的堆、缓存、连接池或文件映射去查。数字只是入口;把它读对,才不会把一次正常的地址预留,当成一场不存在的内存事故。

验证说明:文中命令面向带有 /proc 的 Linux 环境。smaps_rollup 的可用性与权限会受内核和运行环境影响;建议先在测试机选一个已知 PID 运行,再按实际监控口径配置告警。

相关推荐
xixiaoyunya2 小时前
虚拟机备份怎么做:镜像级与文件级搭配的完整实操方案
linux·运维·网络
阿明62 小时前
进程替换【Linux】
linux·运维·服务器
sunoo-2292 小时前
【嵌入式Linux驱动学习】Day1-Day2 全流程梳理:环境搭建→U-Boot→内核→根文件系统→驱动基础
linux·运维·驱动开发·笔记·学习·阿里云·恩智浦
YonyouHRSaaS3 小时前
从安全、信创、运维三方面看,人力资源管理系统哪种部署方式更适合企业HR?
运维·安全·hr系统·人力资源管理系统·hr saas·人事管理系统
倔强的石头1063 小时前
【Linux指南】动静态库系列(十二):库工程发布与错误排查:从能跑到可维护
linux·运维·服务器
wdfk_prog4 小时前
LWIP教程 03:从 `low_level_input()` 到 `pbuf_free()`——`pbuf` 的数据视图、Chain 与引用计数
运维·网络·笔记·学习
掉进电商坑三年没爬出来的东叔4 小时前
云服务器搭建个人网站实战:从零到公网访问,含域名HTTPS
运维·服务器·https
IT大白鼠4 小时前
彭大帅的AI运维助手跨出 Linux:全面接管 Windows 服务器
运维·服务器·人工智能
reasonsummer4 小时前
【办公类-131-05】20261007中2班周计划制作(原资料Word单元格不统一 失败)
运维·服务器