找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5074

积分

0

好友

693

主题
发表于 3 小时前 | 查看: 4| 回复: 0

很多时候,我们在生产环境发布服务后,习惯性地在 Pod 里敲 top,然后按内存占用排序,顺手就把锅甩给排行第一的进程。但这样做往往治标不治本——内存占用最大的进程不一定就是内存泄漏的元凶,频繁使用交换空间也未必代表内存真的吃紧。当 OOM Killer 跳出来时,真正的原因往往是内存压力随时间累积才暴露出来的。

这篇文章,我们就从 top 出发,一直深入到内核级的内存压力、换页行为和 OOM 事件,把排查思路理清楚。

别再只盯着 free

在 Linux 系统里,空闲内存实际上是“浪费”的内存。内核会主动利用未使用的 RAM 做磁盘缓存,也就是我们常看到的 buff/cache。这种做法能有效加速磁盘访问、提升整体性能。因此,当你发现“可用内存好像不够了”时,系统可能完全正常——free 和 top 显示的数字,其实很容易骗人。

很多人在排查内存问题时第一反应就是看 free 的数值,这恰恰是个大坑。free 只统计完全没有被用到的内存,而真正能再分配给程序使用的其实是 available。因为 Linux 会把空闲内存拿去做文件缓存(cache),这些缓存随时可以回收出来给应用。

top - 09:44:22 up 629 days, 19:39,  1 user,  load average: 0.79, 0.62, 0.54
Tasks: 134 total,   1 running, 133 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.2 us,  3.2 sy,  0.0 ni, 94.5 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem :  8008272 total,   130220 free,  3184500 used,  4693552 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  4107028 avail Mem

如果 available 偏低,同时交换分区(swap)的读写持续增长,那系统十有八九正在经历真正的内存压力。接下来要做的,就是定位具体原因:

  • 是某个进程因为内存泄漏或程序 Bug,占用了过多的堆内存?
  • 是在高 I/O 压力下,内核来不及回收缓存?
  • 还是虽然总空闲内存还有剩余,但碎片化严重,导致没法分配大块连续空间?

快速定位

监测 OOM Killer

dmesg -T | grep -E "(Out of memory|oom_kill|Killed process)" | tail -20
[Tue Feb 25 16:42:10 2025] Out of memory: Kill process 20766 (genautomata) score 34 or sacrifice child
[Tue Feb 25 16:42:10 2025] Killed process 20766 (genautomata), UID 0, total-vm:293944kB, anon-rss:286036kB, file-rss:388kB, shmem-rss:0kB
[Tue Feb 25 16:42:11 2025]  [<ffffffffbc9c20cd>] oom_kill_process+0x2cd/0x490
[Tue Feb 25 16:42:11 2025] Out of memory: Kill process 3013 (test) score 34 or sacrifice child
[Tue Feb 25 16:42:11 2025] Killed process 3013 (test), UID 0, total-vm:741780kB, anon-rss:280680kB, file-rss:0kB, shmem-rss:0kB
[Tue Feb 25 16:42:12 2025]  [<ffffffffbc9c20cd>] oom_kill_process+0x2cd/0x490
[Tue Feb 25 16:42:12 2025] Out of memory: Kill process 21049 (cc1plus) score 33 or sacrifice child

OOM Killer 只有在物理内存耗尽,或者内核实在回收不到足够内存时才会触发——也就是说,系统已经到了极限。

当前内存快照

free -m
              total        used        free      shared  buff/cache   available
Mem:           7820        3110         129         401        4580        4010
Swap:             0           0           0

重点盯住可用内存(available)交换分区(swap)的使用情况

  • 可用内存偏低 → 预警信号
  • 可用内存偏低 + 交换分区频繁使用 → 真正的内存压力

查看当前是谁在吃内存

ps aux --sort=-%mem | head -15

如果某个进程吃掉了绝大部分内存,通常逃不出这三种情况:

  • 堆内存配置不当:比如 Java 程序 -Xmx 设得过大,进程被允许占用超出系统实际承载能力的内存。
  • 内存泄漏:程序不断分配内存却不释放,占用随时间不断上涨,最终耗尽系统内存。
  • 正常业务压力超出服务器承载上限:程序逻辑本身没问题,但业务量增长,超出了服务器内存的支撑范围。

按进程检查交换空间使用情况

for pid in /proc/[0-9]*; do
    awk '/VmSwap/{print $2}' $pid/status 2>/dev/null
  done | sort -rn | head -5

这条命令会打印系统上交换内存用量最高的 5 个进程(单位 KB),从大到小排序。

  • 0 — 未使用交换分区(正常)
  • 小规模(例如 1024–10000)——轻度交换使用
  • 大型(100000+)——大量使用交换空间
  • 非常大——很可能是内存压力问题

系统是否在主动换页

vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 7  0      0 121524  70172 4631872    0    0     4    57    0    0  2  3 95  0  0
 0  0      0 121524  70172 4631904    0    0     0     0 40521 97413  2  3 95  0  0
 0  0      0 121400  70172 4631908    0    0     0     0 34207 90644  3  3 94  0  0
 2  0      0 121400  70172 4631912    0    0     0     0 38124 94556  2  4 94  0  0
 1  0      0 120668  70172 4631912    0    0     0    56 35574 92296  3  4 93  0  0

交换分区的活动指标:

  • si:swap in(从磁盘读入内存,单位 KB/s)
  • so:swap out(从内存写回磁盘,单位 KB/s)

如果 si 和 so 持续不为 0,说明系统正在频繁换页,大概率存在内存压力

找到真正的“内存大户”

如果 OOM Killer 已经触发,dmesg 会清楚记录被杀的进程及其内存统计信息,事后分析有明确的入口。但更多时候系统还没崩,这时我们需要找出即将惹事的进程,或是在后台缓慢、持续占内存的进程

检查 RSS(实际物理内存使用情况)

RSS(Resident Set Size)表示一个进程当前实际正在使用的物理内存。这才是我们真正要关注的指标。

ps aux --sort=-%mem | awk 'NR<=10 {printf "%-10s %-8s %s\n", $1, $4, $11}'
USER       %MEM     COMMAND
root       10.3     /xxx
lj7        5.9      vim
root       2.2      /yyy
root       1.5      /zzz
root       1.3      /CloudResetPwdUpdateAgent/depend/jre/bin/java

这样可以快速列出内存消耗最高的进程,排在最前面的自然就是首要怀疑对象。

针对性地检查该进程

有了怀疑对象,就直奔主题。

cat /proc/10623/status | grep -E "Vm(RSS|Peak|Swap|Size)"
VmPeak:     4107708 kB
VmSize:     3518108 kB
VmRSS:       830684 kB
VmSwap:           0 kB

各项指标含义:

  • VmRSS — 进程实际使用的物理内存
  • VmSwap — 进程被换出到交换分区的内存大小
  • VmPeak — 进程内存使用峰值,用于判断内存是否在持续增长

如果 VmSwap 不断上涨,说明这个进程正在加剧系统内存压力。

检查内存映射(用于排查内存泄漏)

pmap -x <PID> | sort -k3 -rn | head -20

这会展示进程的内存分配区域。较大的匿名映射或持续增长的内存段可能意味着:

  • 内存泄漏
  • 大堆分配
  • 未释放的缓冲区

诊断内存泄漏

如果一个进程的内存使用量只涨不跌,那多半存在内存泄漏。正常的应用程序在负载上升时内存增长,但压力降下来后也应该释放内存。

跟踪增长情况

确认内存泄漏最可靠的方法是持续观察进程。单次快照说明不了问题,我们要看内存使用量是:

  • 平稳的
  • 随负载波动的
  • 还是稳步增长、从不回落

这就需要长时间观测,可以写个简单脚本:

PID=12345
while true; do
    echo "$(date +%T) RSS: $(awk '/VmRSS/{print $2}' /proc/$PID/status) kB"
    sleep 5
  done

# Output:
03:21:00 RSS: 524288 kB
03:21:05 RSS: 527360 kB
03:21:10 RSS: 531456 kB  <-- growing each sample, bad sign

或者把数据记录到文件,再计算增量:

while true; do
    echo "$(date +%s) $(awk '/VmRSS/{print $2}' /proc/$PID/status)"
    sleep 10
  done >> /tmp/rss_log.txt &

awk 'NR>1 {print $1, $2, $2-prev} {prev=$2}' /tmp/rss_log.txt | tail -20

使用 valgrindheaptrack 进行内存分析

对于 C/C++ 程序,valgrind --tool=massif 能给出详细的内存分配调用树。但生产环境很难承受 Valgrind 带来的巨大性能开销(可能下降 10-50 倍),这时更轻量的 heaptrack 就登场了,它还能直接 attach 到正在运行的进程上。

# Attach to running process (requires heaptrack installed)
heaptrack --pid <PID>
# ... let it run, Ctrl+C when done

# Analyze the output
heaptrack_print heaptrack.<processname>.<pid>.gz | head -50
PEAK consumption: 6.25 GB

top 5 allocations by peak contribution:
  peak: 4.1 GB, allocations: 892014
    0x7f3a2b... malloc (libc.so)
    0x55f123... build_cache_entry (cache.cpp:247)  <-- likely culprit
    0x55f089... process_request (handler.cpp:112)

# For Java/JVM processes, use jmap
jmap -heap <PID>          # heap summary
jmap -histo:live <PID>    # object histogram (triggers GC first)

理解 /proc/meminfo

/proc/meminfo 暴露了内核的原始内存使用数据。我们常用的内存管理工具(比如 free),底层都从这里读取信息。

cat /proc/meminfo
MemTotal:        8008272 kB
MemFree:          126884 kB
MemAvailable:    4092284 kB
Buffers:           65184 kB
Cached:          4395652 kB
SwapCached:            0 kB
Active:          5130280 kB
Inactive:        2162340 kB
Active(anon):    3096104 kB
Inactive(anon):   146512 kB
Active(file):    2034176 kB
Inactive(file):  2015828 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:             13248 kB
Writeback:             0 kB
AnonPages:       2831788 kB
Mapped:            83476 kB
Shmem:            410828 kB
Slab:             267732 kB
SReclaimable:     221312 kB
SUnreclaim:        46420 kB
KernelStack:       19968 kB
PageTables:        19312 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     4004136 kB
Committed_AS:   11706324 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       29844 kB
VmallocChunk:   34359609340 kB
Percpu:              656 kB
HardwareCorrupted:     0 kB
AnonHugePages:    641024 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      105984 kB
DirectMap2M:     6184960 kB
DirectMap1G:     4194304 kB

看起来字段很多,其实真正关键的只有几个:

  • MemAvailable:系统真正可用的内存
  • Committed_AS / CommitLimit:内存分配失败的风险程度
  • SUnreclaim:可能存在内核内存泄漏的迹象
  • Cached:正常且健康的缓存,完全不用担心

以上便是 Linux 内存排查的核心步骤。如果你有更多实战心得,欢迎到云栈社区交流讨论。




上一篇:凌晨3点,Linux OOM 杀掉核心服务:内核日志解读与防御策略
下一篇:C++防御性编程:用void()防止逗号运算符重载
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-24 22:54 , Processed in 0.688113 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表