内存故障排查

内存故障排查宝典

——从“内存爆了”到定位、修复、验证的完整排查手册

目标:让不懂内存的人,遇到“内存爆满、程序越跑越大、系统越来越卡、Out of Memory、页面文件不足、某个程序吃满内存”等问题,也能按步骤排查。

核心思想:不要看到内存 90% 就直接加内存。先判断“到底是谁占的、占的是什么、为什么一直涨、涨到哪里、重启后是否恢复”。


0. 先学会看懂“内存问题”

很多人看到:

“内存 95%”

就认为:

“内存坏了。”

这是最常见的误区。

内存问题至少可以分成下面几类:

类型通俗解释常见症状优先排查
物理内存不足电脑 RAM 本身不够整机变卡、频繁换页系统/进程
单进程异常占用某个程序吃掉大量 RAM某软件占用几十 GB进程
内存泄漏程序用过的内存不释放,越跑越大刚启动正常,运行几小时越来越大应用
缓存占用系统把 RAM 当缓存使用已用内存高,但可快速回收系统
虚拟内存/页面文件问题RAM 不够时使用磁盘顶上程序卡顿、页面文件告警系统
内核/驱动内存问题Windows 内核或驱动吃内存普通进程加起来并不高,但总内存很高内核
提交内存过高系统承诺给程序的虚拟内存快达到上限“提交限制”接近极限页面文件/进程
句柄/GDI 泄漏程序不断创建对象却不释放内存未必特别高,但程序越来越异常应用/GUI对象
共享/容器/虚拟机限制宿主机或容器给出的内存上限被打满主机看起来正常,应用却 OOM虚拟化/容器
数据结构膨胀程序本身保存了越来越多数据GC 频繁、延迟升高、最终 OOM应用
碎片/连续大块内存不足总量看起来够,但某种分配失败特定大对象/内核分配失败应用/内核

1. 内存排障的总原则

1.1 先判断“是不是内存真的不够”

先问 4 个问题:

问题 A:是整机慢,还是只有一个程序慢?

  • 整机都慢 → 先查系统内存压力
  • 只有一个程序慢 → 先查这个进程
  • 程序运行时间越长越慢 → 高度怀疑泄漏/缓存增长
  • 只有特定操作时瞬时爆 → 高度怀疑峰值分配/大对象

1.2 问题 B:内存是“一直高”,还是“越来越高”?

一直高

可能是:

  • 正常缓存
  • 常驻大程序
  • 数据库/虚拟机本身需要大量内存
  • RAM 太少

越来越高

优先怀疑:

  • 内存泄漏
  • 缓存无限增长
  • 队列积压
  • 连接/会话对象没有释放
  • 文件句柄、GDI、内核对象泄漏

1.3 问题 C:重启后会不会恢复?

重启后恢复,但运行几个小时又爆

强烈怀疑:

软件/服务逐渐积累资源。

这类问题最重要的是:

记录“时间 → 内存 → 进程 → 操作”变化曲线。


1.4 问题 D:任务管理器里的“已用内存”是不是等于“进程加起来”?

不是。

Windows 内存至少要区分:

1
2
3
4
5
6
7
物理 RAM
├─ 进程工作集
├─ 系统/内核
├─ 文件缓存
├─ 内存压缩
├─ 驱动占用
└─ 其他可回收/不可回收内存

所以:

“任务管理器把所有进程加起来只有 10 GB,但电脑已经占了 30 GB”

并不一定是数据错了,剩余部分可能来自系统、缓存、内核、压缩内存等。


2. 一张“10 秒定位”决策树

遇到“内存爆了”时,不要马上杀进程。

按顺序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
内存异常
  │
  ├─ 是整机卡,还是单个程序卡?
  │
  ├─ 重启后恢复吗?
  │
  ├─ 内存是持续上涨,还是瞬间上涨?
  │
  ├─ 哪个进程在涨?
  │
  ├─ 进程涨的是 Working Set / Private Memory / Commit 哪一种?
  │
  ├─ 总内存是否接近物理 RAM 上限?
  │
  ├─ Commit 是否接近 Commit Limit?
  │
  ├─ Pagefile 是否持续高负载?
  │
  └─ 如果进程加起来不高:
       ├─ 查缓存/Standby
       ├─ 查内存压缩
       ├─ 查内核/驱动
       └─ 查非分页池/分页池

3. 内存排障统一流程

以后任何内存问题都尽量按照:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
第 1 步:确认现象
第 2 步:记录基线
第 3 步:确认系统压力
第 4 步:找最大消费者
第 5 步:看它是不是持续增长
第 6 步:区分是哪一种内存
第 7 步:判断是正常需求还是泄漏
第 8 步:临时恢复
第 9 步:长期修复
第 10 步:验证修复是否有效

4. 第 1 层:物理内存层 —— RAM 到底够不够

4.1 这一层管什么

最简单理解:

电脑真正能直接高速工作的内存容量。

例如:

1
电脑安装 32 GB RAM

不代表:

1
永远有 32 GB 可以随便用

因为系统和硬件本身也会使用一部分。


4.2 典型症状

物理内存不足常见表现

  • 整机明显变卡
  • 切换窗口很慢
  • 磁盘持续高占用
  • 程序频繁卡顿
  • 浏览器标签切换明显延迟
  • 程序提示 Out of memory
  • 页面文件使用量明显增加

4.3 Windows 第一检查

图形界面

打开:

1
2
3
4
Ctrl + Shift + Esc
→ 任务管理器
→ 性能
→ 内存

重点看:

1
2
3
4
5
6
7
已使用
可用
提交
缓存
分页池
非分页池
速度

4.4 命令行

1
2
Get-CimInstance Win32_OperatingSystem |
Select-Object TotalVisibleMemorySize,FreePhysicalMemory

换算:

1
2
KB ÷ 1024 = MB
MB ÷ 1024 = GB

4.5 怎么判断

情况 A:内存高,但“可用”仍然不少

不要急着判故障。

可能:

  • 文件缓存
  • Standby
  • 系统缓存

情况 B:可用内存很低 + 系统明显卡顿

重点查:

进程 + 页面文件 + 内存压力

情况 C:可用内存持续下降

重点怀疑:

某个程序或服务在持续吃内存。


5. 第 2 层:进程层 —— 到底是谁吃掉了内存

5.1 最常见问题

用户说:

“电脑内存爆了。”

其实真正的问题可能是:

1
2
3
Chrome.exe      8 GB
某业务程序.exe  12 GB
数据库服务      10 GB

5.2 第一步:任务管理器

1
2
3
任务管理器
→ 进程
→ 点击“内存”排序

先找:

排名最高的 5 个进程。

不要只看第一名。


5.3 PowerShell

查看当前进程工作集:

1
2
3
4
Get-Process |
Sort-Object WorkingSet64 -Descending |
Select-Object -First 20 Name,Id,
@{N='WorkingSetGB';E={[math]::Round($_.WorkingSet64/1GB,2)}}

5.4 正常还是异常?

正常

1
2
3
4
程序本来就需要大量内存
内存占用稳定
业务量增加时合理增长
业务结束后能够下降

可疑

1
2
3
4
程序空闲时仍持续上涨
重启程序后立即恢复
运行时间越长越大
无业务变化但内存不断增长

这种情况:

高度怀疑内存泄漏。


6. 第 3 层:增长曲线层 —— “是不是泄漏”最关键的一步

6.1 什么是内存泄漏

简单理解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
程序申请内存
   ↓
使用内存
   ↓
本来应该释放
   ↓
没有释放
   ↓
继续申请
   ↓
越来越大

例如:

1
2
3
4
5
10:00  500 MB
10:30  900 MB
11:00  1.8 GB
11:30  3.5 GB
12:00  6.7 GB

这就是典型可疑趋势。


6.2 不是“内存一直高”就等于泄漏

例如:

1
2
3
程序启动:2 GB
加载数据:8 GB
之后稳定:8 GB

这不一定是泄漏。

而:

1
2
3
4
程序启动:2 GB
10分钟:3 GB
20分钟:4 GB
30分钟:5 GB

更加值得怀疑。


7. 第 4 层:Windows Working Set / Private Memory / Commit

这是排查内存泄漏时非常重要的一层。

7.1 Working Set

简单理解:

当前实际驻留在物理 RAM 中的那部分内存。


7.2 Private Memory

简单理解:

更适合观察一个进程独占的内存压力。

如果:

1
2
Private Bytes
一直上涨

比单纯 Working Set 上涨更值得警惕。


7.3 Commit

简单理解:

系统已经“承诺”给程序的虚拟内存总量。

它与物理 RAM 不完全相同。

因此:

“RAM 还有一点,但是 Commit 已经接近上限”

依然可能导致 OOM。


8. 第 5 层:提交内存(Commit)异常

8.1 什么叫 Commit 爆了

系统有:

1
2
Commit Charge
Commit Limit

例如:

1
2
Commit = 30 GB
Commit Limit = 32 GB

意味着:

系统距离“虚拟内存承诺上限”已经很近。


8.2 为什么会这样?

常见:

  • RAM 不够
  • 页面文件过小
  • 单个进程虚拟内存不断增加
  • 多个大程序同时运行
  • 内存泄漏

8.3 Windows 查看

任务管理器:

1
2
3
性能
→ 内存
→ 已提交

同时可看:

1
2
3
4
任务管理器
→ 详细信息
→ 选择列
→ 提交大小

然后按“提交大小”排序。


9. 第 6 层:页面文件(Pagefile)问题

9.1 页面文件是什么

可以简单理解为:

RAM 不够时,Windows 在磁盘上准备的一块虚拟内存空间。

它速度远远低于 RAM。

因此:

页面文件不是 RAM 的完全替代品。


9.2 常见症状

  • 内存爆满时磁盘疯狂读写
  • 切换程序明显卡
  • 机器没有完全死机,但响应极慢
  • 内存告警
  • 程序频繁卡住

9.3 检查

打开:

1
2
3
4
5
6
系统属性
→ 高级
→ 性能
→ 设置
→ 高级
→ 虚拟内存

不要看到页面文件大就认为故障。

真正需要关注:

1
2
Commit 是否接近 Limit
系统是否持续发生严重分页

9.4 修复原则

普通用户:

优先使用“自动管理所有驱动器的分页文件大小”。

不建议:

为了“释放磁盘空间”永久关闭页面文件。


10. 第 7 层:缓存 —— “内存高”不一定有问题

很多系统会主动利用空闲 RAM 做缓存。

例如:

1
2
3
RAM 64 GB
应用实际只需要 20 GB
系统可能仍把大量 RAM 放进缓存

这可能是正常行为。


10.1 判断核心

问:

系统在有需要时能不能快速回收?

如果可以:

通常不能仅凭“已用很高”判定故障。


11. 第 8 层:内存压缩

Windows 可能会使用内存压缩来减少分页压力。

任务管理器:

1
性能 → 内存

也可能看到:

1
内存压缩

不要因为“压缩内存存在”就认为系统有故障。

真正应该关注:

  • 是否持续升高
  • 系统是否明显卡顿
  • Page Fault 是否异常
  • Commit 是否接近限制

12. 第 9 层:内核/驱动内存泄漏

这是非常容易被忽略的一类。

典型现象:

1
2
3
任务管理器中没有任何一个程序特别夸张
但是:
总内存越来越高

这时候不要继续杀普通应用。

需要怀疑:

1
2
3
4
5
6
7
8
驱动
内核组件
杀毒软件
VPN驱动
网卡驱动
显卡驱动
文件系统过滤驱动
虚拟化组件

13. Windows 内核内存重点看什么

任务管理器:

1
2
性能
→ 内存

重点:

1
2
分页池
非分页池

13.1 非分页池异常

简单理解:

某些必须始终留在 RAM 中的内核内存。

如果:

1
非分页池不断增长

可能怀疑:

驱动/内核资源泄漏。


13.2 分页池异常

如果:

1
分页池长期不断增长

也需要检查:

  • 驱动
  • 文件系统
  • 安全软件
  • 第三方服务

14. PoolMon —— 排查驱动内存泄漏

Windows 可以使用:

1
poolmon.exe

核心思路不是“看谁最大”,而是:

1
2
3
Tag
Bytes
Allocations

观察:

某个 Tag 的内存是否随时间持续增长。


14.1 排查流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
发现总内存持续增长
 ↓
普通进程加起来不高
 ↓
非分页池明显增长
 ↓
运行 PoolMon
 ↓
找到持续增长的 Tag
 ↓
把 Tag 映射到驱动
 ↓
更新/回滚/卸载对应驱动
 ↓
重新观察

15. 第 10 层:句柄泄漏

某些程序即使 RAM 增长不明显,也可能不断消耗:

1
Handles

长期运行后可能出现:

  • 程序越来越慢
  • 打不开文件
  • 创建对象失败
  • UI 异常
  • 最后程序崩溃

15.1 查看句柄

任务管理器:

1
2
3
详细信息
→ 选择列
→ 句柄

然后观察:

句柄数量是否长期单调增长。


15.2 特征

例如:

1
2
3
程序启动:2,000 handles
1小时:30,000
2小时:80,000

这种持续增长非常可疑。


16. 第 11 层:GDI / USER 对象泄漏

Windows GUI 程序还可能泄漏:

1
2
GDI Objects
USER Objects

症状:

  • 窗口绘制异常
  • 图标显示不正常
  • 新窗口创建失败
  • 程序运行时间越长越异常

任务管理器:

1
2
详细信息
→ 选择列

加入相关列进行观察。


17. 第 12 层:程序自己“正常但太贪”

不是所有高内存都是 Bug。

例如:

数据库

可能主动把大量数据缓存到内存。

浏览器

标签页、扩展、页面缓存都可能占内存。

JVM

可能配置了较大的 Heap。

.NET

GC 不一定会把物理内存马上还给操作系统。

Python

内存释放后进程的 RSS 不一定立即明显下降。

因此:

看到进程内存下降不明显,不应该直接判定“泄漏”。

真正要看:

1
2
3
4
对象是否持续增长
堆是否持续增长
分配速度是否持续增长
GC 后是否仍持续上升

18. 第 13 层:应用内存泄漏定位

如果确认:

1
某软件内存随时间持续增长

进入应用层排查。


18.1 第一优先级:能否稳定复现?

记录:

1
2
3
4
5
6
7
8
9
启动程序
↓
执行固定操作
↓
等待
↓
重复
↓
记录内存

例如:

1
2
3
4
循环打开/关闭窗口 100 次
循环加载/卸载文件 100 次
循环登录/退出 100 次
循环创建/关闭连接

如果:

1
每轮操作 + 10 MB

比单纯“偶尔爆一次”更容易定位。


19. 第 14 层:不同语言的泄漏定位

C/C++

重点怀疑:

  • malloc/new 后没有 free/delete
  • 引用链
  • 容器持续增长
  • 缓存无上限
  • 第三方库泄漏

工具:

1
2
3
4
AddressSanitizer
Valgrind
Visual Studio Diagnostic Tools
Application Verifier

Java

重点:

1
2
3
4
5
Heap
Old Gen
Metaspace
Direct Buffer
Thread

常见工具:

1
2
3
4
5
6
jcmd
jmap
jstack
JFR
MAT
VisualVM

典型思路:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Heap 不断增长
 ↓
Full GC 后仍下降不下来
 ↓
Dump
 ↓
MAT 看 Dominator Tree
 ↓
找最大 Retained Size
 ↓
找引用链

.NET

重点:

1
2
3
4
Managed Heap
Gen 0 / Gen 1 / Gen 2
LOH
GC

工具:

1
2
3
4
5
dotnet-counters
dotnet-dump
dotnet-gcdump
PerfView
Visual Studio

Python

重点:

1
2
3
4
5
6
对象数量
容器增长
全局变量
缓存
循环引用
C 扩展

工具:

1
2
3
4
tracemalloc
gc
objgraph
memory_profiler

20. “内存泄漏”最常见的 10 个根因

根因特征
容器无限增长list/map/cache 越来越大
全局缓存不清理用户/请求越多越大
线程泄漏线程数不断增加
连接泄漏TCP/数据库连接不断增加
文件句柄泄漏handles/fd 持续增加
事件订阅未取消对象一直无法回收
回调引用对象被长期持有
定时器未释放任务越积越多
大对象未释放大数组/Buffer长期保留
第三方库/驱动 Bug应用代码看似正常

21. 第 15 层:线程泄漏

这是经常与内存泄漏一起发生的问题。

观察:

1
2
3
进程内存
线程数
句柄

如果三者一起持续增加:

非常值得怀疑资源泄漏。

Windows:

1
2
3
Get-Process |
Sort-Object Threads -Descending |
Select-Object -First 20 Name,Id,Threads

22. 第 16 层:文件/连接资源泄漏

某程序不断创建:

1
2
3
4
5
6
文件
Socket
数据库连接
线程
Timer
Handle

这些资源最后都可能间接造成内存压力。

排查方法:

1
2
3
4
5
6
7
内存
+
句柄
+
线程
+
连接数

一起看。


23. “程序内存暴涨”的五种形态

形态 A:启动就高

1
2
启动 → 8 GB
之后稳定

优先怀疑:

配置/正常数据规模。


形态 B:缓慢上涨

1
1 GB → 2 → 3 → 4 → 5 GB

优先:

泄漏。


形态 C:操作一次暴涨,然后不回落

1
2
3
4
5
6
7
8
9
2 GB
↓
加载大文件
↓
10 GB
↓
操作结束
↓
9.8 GB

重点:

缓存 / 大对象保留 / GC行为。


形态 D:瞬间暴涨并崩溃

1
2
3
4
5
6
7
2 GB
↓
处理大数据
↓
30 GB
↓
OOM

重点:

峰值分配 / 大对象 / 批量处理。


形态 E:进程不高,但系统总内存不断涨

重点:

内核、驱动、缓存、压缩内存、虚拟化。


24. 第 17 层:Windows 事件日志 / 崩溃记录

出现 OOM、应用崩溃后:

打开:

1
2
3
4
事件查看器
→ Windows 日志
→ 应用程序
系统

重点查:

1
2
3
Application Error
Resource-Exhaustion-Detector
Kernel-Power

不要只看“错误”两个字。

要结合:

1
2
3
4
时间
进程
故障模块
内存压力

25. 第 18 层:Resource-Exhaustion-Detector

如果系统出现:

“Windows 检测到内存不足……”

可以重点查看:

1
2
3
事件查看器
→ Windows 日志
→ 系统

查找:

1
Resource-Exhaustion-Detector

它可以帮助回答:

当时哪个进程占用了大量内存。


26. 第 19 层:Perfmon 性能监控

如果问题:

不是现在爆,而是过几小时/几天才爆。

不要一直盯任务管理器。

应该做:

长期采样。

Windows:

1
perfmon

建议记录:

1
2
3
4
5
6
7
Memory\Available MBytes
Memory\% Committed Bytes In Use
Paging File\% Usage
Process(*)\Private Bytes
Process(*)\Working Set
Process(*)\Handle Count
Process(*)\Thread Count

27. 最重要:做“内存增长曲线”

例如:

时间总内存进程 A进程 BCommitHandle
10:0048%1.2G0.8G35%3K
10:3055%2.1G0.8G42%5K
11:0063%3.1G0.9G50%8K
11:3072%4.3G0.9G60%13K
12:0085%6.1G1.0G77%21K

很明显:

A 进程、Commit、Handle 一起上涨。

这比一句“内存爆了”有价值很多。


28. 修复原则:先临时恢复,再永久修复

28.1 临时恢复

可以:

1
2
3
4
5
结束异常进程
重启服务
重启程序
重启电脑
清理无用任务

但必须认识到:

重启不是修复泄漏。

重启只相当于:

1
把内存清空

然后问题可能再次出现。


29. 什么时候可以直接杀进程?

适合:

  • 明确知道程序已经失控
  • 工作已保存
  • 这是可重启服务
  • 当前目标是恢复系统可用性

不适合:

  • 不知道是什么进程
  • 数据库正在写入
  • 文件正在保存
  • 是系统关键进程
  • 生产核心服务

30. 长期修复方法

应用泄漏

1
2
3
4
5
6
7
复现
→ 抓内存快照
→ 找增长对象
→ 找引用链
→ 修改代码
→ 压测
→ 长时间运行

驱动泄漏

1
2
3
4
5
6
找 Pool Tag
→ 对应驱动
→ 更新驱动
→ 回滚驱动
→ 卸载相关软件
→ 验证

配置导致的高内存

例如:

1
2
3
4
缓存上限没设置
JVM Heap过大
数据库 Buffer 配置过大
容器限制不合理

修复:

给资源设置合理上限。


31. 生产环境最重要的修复原则

不要只问:

“怎么让它不爆?”

应该问:

“为什么它会爆?”

以及:

“如果再次出现,系统能不能自动恢复?”

建议:

1
2
3
4
5
6
7
8
9
监控
+
告警
+
限制
+
自动重启
+
泄漏修复

32. 自动防护

对于服务程序,可以设计:

1
2
3
4
内存 > 70% → 告警
内存 > 80% → 检查异常进程
内存 > 90% → 触发保护
内存 > 95% → 按策略重启非核心服务

注意:

自动重启是兜底,不应替代根因修复。


33. “整个系统卡死”的应急处理

优先级:

1
2
3
4
5
6
7
1. 保存能保存的数据
2. 找异常进程
3. 停止非核心大内存程序
4. 如果系统已经无法操作:
   → Ctrl + Alt + Del
   → 任务管理器
5. 最后才考虑重启

如果是服务器:

不要直接重启生产服务器,先确认业务、集群和高可用状态。


34. Linux 内存问题快速排查

如果系统不是 Windows,而是 Linux:

看总体

1
free -h

重点:

1
2
3
4
5
6
total
used
free
buff/cache
available
swap

最重要:

available

不要只看 free。


看进程

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

持续观察

1
watch -n 2 free -h

看进程增长

1
watch -n 2 'ps -eo pid,comm,%mem,rss --sort=-rss | head -20'

看 Swap

1
swapon --show

看 OOM

1
dmesg | grep -i oom

或:

1
journalctl -k | grep -i oom

如果看到:

1
2
Out of memory
Killed process

说明:

内核 OOM Killer 已经杀进程。


35. Linux cgroup / Docker 容器内存 OOM

容器里非常常见:

1
2
宿主机有 64 GB
Docker 容器只有 2 GB 限制

结果:

容器自己 OOM。

所以:

宿主机没爆 ≠ 容器没爆。


Docker

1
docker stats

检查:

1
2
3
MEM USAGE
MEM %
LIMIT

36. Java OOM 排查口诀

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Java进程越来越大
 ↓
看 Heap
 ↓
Full GC 后是否回落
 ↓
不回落
 ↓
Heap Dump
 ↓
MAT
 ↓
Dominator Tree
 ↓
Retained Size
 ↓
找引用链

特别注意:

Java 的 OutOfMemoryError 不一定都是 Java Heap 爆满。

也可能:

  • Metaspace
  • Direct Buffer
  • Native Memory
  • Thread
  • 文件句柄等

37. “OOM”不一定只有一种

看到:

1
OutOfMemoryError

不要直接理解为:

“RAM 没了。”

可能实际上是:

1
2
3
4
5
6
Heap OOM
Metaspace OOM
Direct Memory OOM
Native Memory OOM
Address Space OOM
Container Memory Limit

所以第一件事:

先看完整错误信息。


38. 常见故障案例

案例 1:电脑开机正常,下午越来越卡

现象:

1
2
3
8:00 正常
12:00 开始卡
15:00 内存 95%

排查:

1
2
3
任务管理器排序
→ 每30分钟记录内存
→ 找出增长最快进程

结论:

某程序长期泄漏。

修复:

升级软件/修复程序/重启服务作为临时措施。


案例 2:任务管理器没有大进程,但内存一直高

排查:

1
2
3
4
5
6
7
总内存
↓
进程总和
↓
非分页池/分页池
↓
驱动

如果:

1
2
Nonpaged Pool
持续上涨

重点:

驱动泄漏。


案例 3:内存 90%,但电脑并不卡

先看:

1
2
3
Available
Cached
Commit

可能:

大量内存只是缓存。

不要因为数字高就强行“清内存”。


案例 4:只有某服务运行 6 小时后越来越大

验证:

1
2
3
4
5
6
7
重启服务
↓
内存恢复
↓
继续运行
↓
再次增长

这非常有价值。

说明:

问题高度集中在这个服务生命周期内。


案例 5:程序占用不算特别大,但系统提示内存不足

排查:

1
2
3
Commit
Pagefile
Kernel

可能:

提交内存接近上限,而不是单个 Working Set 特别大。


案例 6:处理一个大文件瞬间 OOM

例如:

1
2
500 MB 文件
→ 程序读取成 8 GB 对象

这更可能是:

峰值内存需求问题。

修复:

1
2
3
4
流式读取
分页处理
分块处理
减少中间副本

案例 7:浏览器越来越吃内存

排查:

1
2
3
4
5
浏览器
→ 标签页
→ 扩展
→ GPU进程
→ 单个网页

使用:

1
浏览器自身任务管理器

先找:

是哪个 Tab / 扩展。


案例 8:服务器运行几天后爆内存

优先做:

1
2
3
4
5
6
7
性能计数器
+
进程 Private Bytes
+
Handle Count
+
Thread Count

不要等爆了才查。


案例 9:升级显卡驱动后开始内存异常

对比:

1
2
升级前
升级后

如果时间高度相关:

回滚/更新驱动并验证。


案例 10:Docker 容器 OOM,但主机内存还有很多

检查:

1
docker stats

如果:

1
容器使用 > limit

说明:

是容器限制问题,不一定是主机 RAM 不够。


39. 一页判断口诀

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
内存高 ≠ 内存泄漏

持续上涨 → 查泄漏
重启恢复 → 看生命周期
单进程高 → 查进程
总量高、进程不高 → 查内核/缓存
Commit 高 → 查虚拟内存
Pagefile 重 → 查分页压力
Nonpaged Pool 高 → 查驱动
Handle 高 → 查资源泄漏
Thread 高 → 查线程泄漏
瞬间暴涨 → 查峰值分配
容器 OOM → 查容器限制

40. Windows 命令速查表

目的命令
查看进程内存Get-Process
内存排序Get-Process | Sort-Object WorkingSet64 -Descending
查看线程Get-Process | Sort-Object Threads -Descending
系统内存Get-CimInstance Win32_OperatingSystem
查看进程详细信息任务管理器“详细信息”
性能监控perfmon
事件查看器eventvwr
PoolMonpoolmon.exe
查看系统信息msinfo32

41. Linux 命令速查表

目的命令
总内存free -h
进程内存排序ps aux --sort=-%mem
持续观察watch -n 2 free -h
Swapswapon --show
OOMdmesg | grep -i oom
内核日志journalctl -k
Dockerdocker stats
内存详细信息cat /proc/meminfo

42. 不能乱做的操作

不要看到内存高就立刻:

1
关闭页面文件

不要:

1
为了“释放内存”频繁运行所谓内存清理工具

不要:

1
不知道进程是什么就 taskkill

不要:

1
生产环境直接重启

不要:

1
把重启当成泄漏修复

43. 排障时必须记录什么

最少记录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
时间:

故障现象:

物理内存总量:

已用:

可用:

Commit:

Pagefile:

最大进程:

最大进程内存:

进程 Private Memory:

Working Set:

Nonpaged Pool:

Paged Pool:

Handles:

Threads:

是否重启恢复:

恢复后多久再次出现:

44. 内存泄漏排障记录模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
【基本信息】

机器:
系统:
RAM:
程序:
版本:

【故障】

开始时间:
发生频率:
是否可复现:

【初始状态】

总内存:
可用:
Commit:
Pagefile:

【进程】

PID:
进程:
Working Set:
Private Memory:
Commit:
Handles:
Threads:

【变化】

10:00:
10:30:
11:00:
11:30:
12:00:

【判断】

问题类型:
怀疑层级:
怀疑对象:

【处理】

临时措施:

永久措施:

【验证】

运行多久:
是否再次增长:
增长速度:
是否达到原来的峰值:

45. 最终排障路线

以后遇到任何“内存爆了”,直接执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
① 看总内存
   ↓
② 看可用内存
   ↓
③ 看 Commit
   ↓
④ 按进程内存排序
   ↓
⑤ 找最大的进程
   ↓
⑥ 记录它的 Working Set / Private Memory
   ↓
⑦ 观察 10~30 分钟
   ↓
⑧ 判断“稳定高”还是“持续涨”
   ↓
⑨ 如果持续涨:
       查泄漏
   如果进程不涨:
       查缓存/内核/驱动
   如果 Commit 爆:
       查虚拟内存
   如果瞬间爆:
       查峰值分配
   如果容器爆:
       查限制
   ↓
⑩ 临时恢复
   ↓
⑪ 找根因
   ↓
⑫ 修改
   ↓
⑬ 长时间验证

46. 一句话总结

排查内存不要先问“怎么清内存”,要先问“谁在吃、吃的是什么、为什么一直吃、重启后为什么又回来”。

真正专业的内存排障,不是把“内存使用率”降下来,而是能够回答:

1
2
3
4
5
6
7
谁占了内存?
占了多少?
占的是哪种内存?
为什么占?
是否持续增长?
是否可以释放?
如何证明修好了?

这 7 个问题能回答清楚,内存问题基本就从“猜”进入了“证据驱动的定位”。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 Stack 由 Jimmy 设计