一、问题现象

日常使用中发现内存占用异常:

指标 数值
物理内存 32.0 GB DDR5
已使用(已压缩) 28.9 GB(924 MB)
占用率 93%
可用 2.3 GB
已提交 39.5 / 52.2 GB
分页缓冲池 1.9 GB
非分页缓冲池 3.6 GB

矛盾点:任务管理器里没有任何一个进程占用突出,浏览器、编辑器加起来也不该有这么多。这是典型的"进程之和 ≠ 系统已用"场景。


二、排查方法论:为什么要先分层

很多人一上来就打开任务管理器按内存排序,这在本例中完全无效——因为泄漏根本不在用户态。

Windows 内存可以粗分为三层,排查必须自上而下:

┌─────────────────────────────────────┐
│ 应用层  Process Private             │  ← 任务管理器能看到
├─────────────────────────────────────┤
│ 虚拟化层 Vmmem / WSL2 / VMware      │  ← 任务管理器看得到但容易忽略
├─────────────────────────────────────┤
│ 内核层  Paged/Nonpaged Pool         │  ← 任务管理器几乎看不到 ★
│         Page Table / Driver Locked  │
└─────────────────────────────────────┘

核心判断动作:把所有进程内存加总,和系统报告的"已使用"对比。差额落在哪一层,就往哪一层挖。

powershell

# 进程侧总和
[math]::Round((Get-Process | Measure-Object WS -Sum).Sum/1GB, 2)

三、第一步:确认内存到底去哪了(RAMMap)

工具:SysInternals RAMMap,打开后看 Use Counts 页。

实测数据:

项目 实测值 正常范围 判定
Process Private(全部进程) 6.14 GB ✅ 正常
Page Table(页表) 5.79 GB 200–500 MB 🔴 严重异常
Nonpaged Pool 2.75 GB < 1 GB 🔴 异常
Paged Pool 1.69 GB < 1 GB 🟡 偏高
Driver Locked 0.67 GB 少量 🟡 偏高
Mapped File 5.15 GB 视情况 🟡 偏高
Metafile(NTFS 元数据) 1.50 GB 可回收 ⚪ 忽略

结论:所有进程加起来才 6.14 GB,应用层彻底清白。内核相关合计约 11.8 GB,占 32G 的近 40%(正常应在 2–3 GB)。

关键推算:页表异常到什么程度

x64 架构下,每 4KB 页对应 8 字节 PTE,页表约占已映射内存的 0.2%

已提交 39.7 GB × 0.2% ≈ 80 MB   ← 理论值
实测                     5.79 GB  ← 超出 70 倍以上

这个数量级的偏差,只可能来自"大量地址空间被创建后没有被拆除"。

⚠️ 本文最大的一次误判:此处我一度认为只有虚拟机的 EPT/NPT 嵌套页表能解释,加上机器上确实有两块 VMware 虚拟网卡,于是锁定 VMware。这个判断在下一步被直接推翻。教训见第十一节。


四、第二步:定位内核池中的具体对象(poolmon)

poolmon.exe 随 Windows Driver Kit 提供,路径通常在:

C:\Program Files (x86)\Windows Kits\10\Tools\<版本>\x64\poolmon.exe

必须以管理员身份运行,进入后按键操作:

按键 作用
P 切换 Paged / Nonpaged / 全部
B 按 Bytes 降序排序 ★
D 按 Diff 排序
Q 退出

💡 默认是按 Tag 字母序排列的,不按 B 排序等于什么都没看到

排序后第一行直接给出答案:

Tag   Type   Allocs      Frees   Diff      Bytes        Per Alloc
Proc  Nonp   160,498     2       160,496   575,067,056  3,583

数据解读

Proc 标签代表进程对象(EPROCESS)。正常 Win11 同时运行 200–400 个进程,Diff 应该就在这个量级。

实测 160,496 —— 这些进程早已退出,但内核对象没有被释放,即僵尸进程(zombie process)

更刺眼的是 Frees = 2:开机 28 小时,只释放过 2 个进程对象。不是释放得慢,是几乎完全没释放。

交叉验证:三个标签完全对齐

Tag 类型 Diff 占用 含义
Proc 非分页 160,496 548 MB 进程对象 EPROCESS
MiP2 非分页 160,497 235 MB 每进程内存管理结构
Toke 分页 166,563 284 MB 进程访问令牌
Thre 非分页 14,271 47 MB 线程对象
SeAt 分页 681,704 66 MB 安全属性

三个数字(16.0 万 / 16.0 万 / 16.6 万)几乎完全对齐——每个僵尸进程恰好持有一个 EPROCESS、一个 MI 结构、一个令牌。这不是巧合,是同一个泄漏的三个侧面。

页表之谜由此解开

5.79 GB ÷ 160,496 ≈ 37 KB/进程 ≈ 9 个页面

Windows 在进程退出时会拆除地址空间,但只要进程对象未被完全解引用,顶层页目录和部分页表页就必须保留(内核仍需要 DirectoryTableBase)。37 KB 正是一个僵尸进程残留页表结构的典型大小。

内存去向至此完全对上账:

页表残留      5.79 GB   ← 16 万僵尸进程 × 37 KB
非分页池      2.61 GB   ← Proc / MiP2 / Thre 等
分页池        1.94 GB   ← Toke / SeAt 等
──────────────────────
合计约       10.3 GB    全部源自同一个泄漏

五、第三步:排除用户态句柄泄漏

僵尸进程不消失,通常是有人拿着它们的句柄。先查用户态:

powershell

Get-Process | Sort HandleCount -Desc | Select -First 15 Name,Id,HandleCount,@{n='内存MB';e={[int]($_.WorkingSet64/1MB)}} | ft -AutoSize

实测结果:

Name          Id      HandleCount  内存MB
----          --      -----------  ------
System        4       9494         32
explorer      347268  7809         474
lsass         2540    2606         64
dwm           381780  2435         108
Weixin        410028  2326         297
...

全部正常,最高不过 9494,所有进程加起来约 8–12 万句柄。

这个"阴性结果"反而是关键线索

它排除了用户态,把结论收紧为:

那 16 万个进程对象是被内核驱动用**对象引用(reference)**攥着的,不是句柄。

引用计数在用户态完全不可见——任务管理器、Process Explorer 都查不到。这也解释了为什么僵尸进程能悄无声息堆到 16 万。


六、第四步:抓进程创建源头

既然对象在持续增加,先算泄漏速率,判断紧急程度:

powershell

$b=(Get-CimInstance Win32_OperatingSystem).LastBootUpTime; $u=(Get-Date)-$b; "开机: $b"; "已运行: $([math]::Round($u.TotalHours,1)) 小时"; "泄漏速率: $([math]::Round(160496/$u.TotalHours)) 个/小时"

输出:

开机: 07/23/2026 12:54:15
已运行: 28.3 小时
泄漏速率: 5668 个/小时

5668 个/小时 ≈ 每秒 1.6 个进程。 正常空闲的 Win11 大约每分钟几个,高了两个数量级。

好消息是:这个速率意味着不用等,一分钟就能抓到现行

用 WMI 事件订阅实时监控进程创建:

powershell

Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -SourceIdentifier PS1 | Out-Null; Start-Sleep 60; $e=Get-Event -SourceIdentifier PS1 -EA 0; "60秒内共创建 $($e.Count) 个进程"; $e | %{ $_.SourceEventArgs.NewEvent.ProcessName } | Group -NoElement | Sort Count -Desc | Select -First 12 | ft Count,Name -AutoSize; Unregister-Event -SourceIdentifier PS1; Get-Event -SourceIdentifier PS1 -EA 0 | Remove-Event

结果:

60秒内共创建 150 个进程
Count Name
----- ----
   59 git.exe        ← 主犯
   59 conhost.exe    ← 陪绑(每个控制台程序都会带一个)
   29 taskkill.exe   ← 关键线索
    2 WmiApSrv.exe
    1 msedge.exe

三个名字连起来看

  • git.exe : conhost.exe = 59 : 59 → 是某个图形界面程序在反复调起 git 命令行,每次都要新建控制台宿主
  • taskkill.exe = 29,约为 git 的一半近一半的 git 调用卡住了,被强制杀掉

这是典型的"轮询 + 超时强杀"循环。


七、第五步:定位父进程

进程名只说明"是什么",还要知道"谁调的":

powershell

Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -SourceIdentifier PS2 | Out-Null; Start-Sleep 30; $e=Get-Event -SourceIdentifier PS2 -EA 0; $e | %{ $n=$_.SourceEventArgs.NewEvent; $p=(Get-Process -Id $n.ParentProcessID -EA 0).Name; "{0}  <-  {1} (PID {2})" -f $n.ProcessName,$p,$n.ParentProcessID } | Group -NoElement | Sort Count -Desc | Select -First 12 | ft Count,Name -AutoSize; Unregister-Event -SourceIdentifier PS2; Get-Event -SourceIdentifier PS2 -EA 0 | Remove-Event

结果:

Count Name
----- ----
   19 git.exe       <-  ChatGPT (PID 615016)
   18 taskkill.exe  <-  ChatGPT (PID 615016)

元凶:ChatGPT 桌面版(Codex 智能体)。

注意这个比例:19 次调用 git,18 次被 taskkill 强杀,接近 1:1。意味着它启动的 git 进程基本没有一个正常退出过。这不是"轮询有点勤",是 git 集成功能在死循环里空转。


八、第六步:抓完整命令行,锁定根因

知道是谁调的还不够,要知道它在扫什么:

powershell

1..40 | %{ Get-CimInstance Win32_Process -Filter "Name='git.exe'" -EA 0 | select -Exp CommandLine; Start-Sleep -m 500 } | Group -NoElement | Sort Count -Desc | Select -First 5 | ft -AutoSize

结果:

30  git.exe -c core.hooksPath=NUL -c core.fsmonitor= ls-files --others --exclude-standard -z -- .VirtualBox/ ...
30  C:\Files\Git\bin\git.exe -c core.hooksPath=NUL -c core.fsmonitor= ls-files --others --exclude-standard ...
 4  C:\Files\Git\bin\git.exe ... status --no-renames --ignored=matching --untracked-files=all ...

命令行里藏着全部答案

.VirtualBox/ 是典型的用户主目录下的配置文件夹,说明扫描范围是 C:\Users\<用户名>

验证:

powershell

Test-Path "$env:USERPROFILE\.git"     # 返回 True → 主目录被 git init 过

再看参数组合——恰好是 git 里最昂贵的:

参数 作用 后果
--untracked-files=all 逐个列出未跟踪文件,不折叠目录 必须遍历每一个文件
--ignored=matching 连被忽略的文件也要列 .gitignore 的过滤优化失效
-c core.fsmonitor= 显式禁用文件系统监视器 无法增量,每次全量重扫
-c core.hooksPath=NUL 禁用 hooks(出于安全,合理)

主目录里有什么:anaconda3\(30–50 万个小文件)、AppData\.cache\、各种 node_modules\.VirtualBox\,而且根本没有 .gitignore

这个任务从设计上就不可能在 1 秒内完成。


九、根因分析:git 的"向上查找"机制

这是整件事最容易被忽略、也最值得记住的一环。

git 会一层层往上找 .git

在任何目录执行 git 命令,git 会从当前目录开始逐级向上查找 .git 文件夹,找到的第一个就是仓库根

于是:

工作区设为  C:\Users\17564\Documents\项目\
        ↓  这里没有 .git
向上找:    C:\Users\17564\Documents\      没有
继续向上:  C:\Users\17564\                ← 找到 .git!
        ↓
git 认定仓库根 = C:\Users\17564
        ↓
扫描范围 = 整个用户主目录(50 万+ 文件)

所以你可能压根没把主目录设成工作区,只要工作区是主目录下的任意子目录、且它自己没有 .git,范围就会自动膨胀到整个 C:\Users\<用户名>

主目录下有 .git污染其下所有子目录的 git 行为——这是本例的核心机制。

完整因果链

2024 年某次误操作,在 C:\Users\17564 执行了 git init
        ↓
Codex 工作区落在主目录(或其子目录)
        ↓
git 向上查找,认定仓库根 = 整个用户主目录
        ↓
每秒执行一次 git status --untracked-files=all(遍历 50 万文件)
        ↓
远超 1 秒超时 → taskkill /F 强杀
        ↓
被强杀的进程被内核驱动挂着引用,EPROCESS 无法释放
        ↓
28 小时累积 16 万僵尸进程
        ↓
页表 5.79G + 非分页池 2.6G + 分页池 1.9G ≈ 10 GB
        ↓
32G 内存可用仅剩 2.3G

一个讽刺的细节

git 本来有 core.untrackedCachefsmonitor 两套缓存机制,专门解决大仓库扫描慢的问题。但 Codex 用 -c core.fsmonitor= 主动关掉了(大概是为保证状态绝对新鲜、不受本地配置干扰),等于自断退路。在正常小仓库里这个选择没什么代价,碰上被误 init 的主目录就成了灾难。

责任划分

问题
用户侧 两年前在主目录执行了 git init,平时无人触碰,一直未暴露
应用侧 未对仓库规模做检查;超时后无退避机制,无脑重试;强杀后不做清理
系统侧 疑似有内核驱动持有进程对象引用不放(Frees = 2 极不正常)

单独任何一方都不足以造成 10 GB 损失。是两年前的一个 git init 撞上智能体的无限重试,才在 28 小时里堆出 16 万僵尸进程。


十、解决方案与验证

10.1 解除根因(改名而非删除)

cmd

:: 1. 先确认仓库里有没有需要保留的内容
git -C C:\Users\17564 log --oneline -10
git -C C:\Users\17564 remote -v

:: 2. 退出 ChatGPT 客户端,并清理残留 git 进程
taskkill /F /IM git.exe

:: 3. 改名(非破坏性,随时可回滚)
cd /d C:\Users\17564
attrib -h .git
ren .git .git_backup_20260724

:: 4. 验证
dir /a C:\Users\17564\.git*
git -C C:\Users\17564 status

期望输出:

fatal: not a git repository (or any of the parent directories): .git

⚠️ 两个注意点

  1. 务必用 ren 而不是 rd /s。若仓库启用过 Git LFS,.git\lfs 里可能存着大文件的唯一副本。
  2. 别把 .gitconfig 当成仓库。它是 git 的全局配置文件(用户名、邮箱、别名),每台装了 git 的机器都有,不要动

10.2 遇到"拒绝访问"怎么办

改名报拒绝访问 = 目录被进程占用,不是命令写错。排查顺序:

  1. 彻底退出 ChatGPT / Codex 客户端(含托盘)
  2. 关闭 VS Code / Cursor 等会读 Git 状态的编辑器
  3. taskkill /F /IM git.exe 清理残留子进程
  4. 检查杀软实时扫描、OneDrive 同步、SearchIndexer.exe
  5. 精确定位:Process Explorer → Ctrl+F → 搜 \.git → 查看持有句柄的进程

attrib -h / attrib -s 对重命名不是必需的——隐藏/系统属性不阻止 rename,关键在于解除占用。

10.3 验证效果

采样 60秒总进程数 git.exe taskkill.exe
修复前 150 59 29
修复后第一次 7 0 1
修复后第二次 2 0 0

git.exe 完全归零——Codex 检测到工作区不再是 git 仓库,直接放弃轮询,连尝试都不再发起。

泄漏速率:5668 个/小时 → ≈ 0

10.4 必须重启

⚠️ 改名不会释放已泄漏的内存。

那 10 GB 锁在内核的 16 万个僵尸进程对象上,没有任何用户态工具能清理内核对象。必须重启一次,内存才会从 93% 回落到正常的 30–40%。

正确顺序:改名 → 验证 taskkill 归零 → 重启

10.5 加固措施(防复发)

措施 说明
改工作区目录 把 Codex 工作区指向具体项目文件夹,而非主目录。否则哪天再 git init 一次问题原地复活
关闭"完全访问权限" 无审批的全盘读写权限,风险与收益不成比例
智能体环境改 WSL/容器 子进程创建在虚拟环境内,不污染宿主机内核池
定期检查主目录 Test-Path "$env:USERPROFILE\.git" 应为 False

10.6 遗留问题:次日复查

本次排查有一个尚未闭环的疑点:

Proc   Allocs 160,498   Frees 2

这个计数是全系统的。开机 28 小时,正常启动又退出的程序不计其数,这些全都应该被回收,结果只释放了 2 个。

正常情况下,即使进程被 taskkill /F 强杀,内核也会回收其 EPROCESS。释放率接近零本身就不正常,暗示可能有内核驱动持有对象引用不放。

即:

驱动不释放引用   ← 真正的漏洞(基础病)
      ×
Codex 每秒拉一个 git  ← 放大器(加速剂)
      ↓
28 小时 10 GB

拆掉放大器后,如果基础病仍在,泄漏依然存在,只是慢得多。

场景 速率 每月增长估算
修复前 5668/小时 几天就撑爆
修复后 + 驱动仍泄漏 ~60/小时 ~2.8 GB
修复后 + 驱动正常 ~60/小时 ≈ 0

复查方法:正常使用 12 小时以上,重新运行 poolmonPB,查看 Proc 行:

  • Diff 几百,Frees 与 Allocs 接近 → 彻底解决
  • Diff 上万,Frees 仍是个位数 → 驱动泄漏独立存在,需单独排查

驱动嫌疑名单(按本机情况):Armoury Crate 全家桶、KLOG 标签对应的安全驱动、AMD + NVIDIA 双显卡驱动。


十一、踩坑记录

真实排查不是一条直线,把走错的路也记下来,比只记结论有用。

坑 1:被"虚拟机"假设带偏(最大的一次误判)

看到 Page Table 5.79 GB 时,我推断"超出理论值 70 倍只可能是虚拟机的 EPT/NPT 嵌套页表",加上任务管理器里确实有两块 VMware 虚拟网卡,于是锁定 VMware。

这个判断是错的。 那两块网卡只是安装后残留的虚拟适配器,不占内存。

教训:证据链看似闭合时,更要警惕。poolmon 排序后的第一行(Proc Diff = 16 万)直接推翻了假设——数据永远优先于推理。页表膨胀不止虚拟机一种成因,僵尸进程残留同样能造成,而且本例中每进程 37 KB 的数字比 EPT 假设吻合得多。

坑 2:poolmon 不按 B 排序等于白开

默认按 Tag 字母序,满屏都是 0xG31MPP 这类无意义小条目。必须按 P 筛类型 + 按 B 排序,大户才会浮到顶部。

坑 3:PowerShell 多行粘贴导致行序错乱

在旧版 PowerShell 控制台(尤其叠加 Anaconda 环境)里粘贴多行命令,可能出现执行顺序颠倒:

尝试除以零。
找不到"op_Subtraction"的重载,参数计数为:"2"

现象是最后一行先执行,变量还没赋值。

解决:所有命令压成单行,用 ; 连接;或改用 Windows Terminal。

行尾是 | 时 PowerShell 会显示 >> 续行提示符等待输入——此时按一次空回车即可执行,Ctrl+C 放弃。

坑 4:先重启会毁掉证据

重启确实能立刻拿回 10 GB,但泄漏证据全部消失,得重新等它累积。

正确顺序:先取证(poolmon + 速率计算 + 进程监控)→ 定位 → 修复 → 最后才重启。

坑 5:用户态查不到就以为没问题

句柄排序全部正常(最高 9494),很容易得出"没有泄漏"的结论。

实际上对象引用计数在用户态完全不可见,Process Explorer 也看不到。阴性结果不等于没问题,而是指向了更深的层次


十二、方法论沉淀

通用排查流程

1. 分层验证:进程总和 vs 系统已用
        ↓ 差额在哪一层?
2. RAMMap Use Counts:确定异常项
        ↓ Page Table / Nonpaged Pool 异常?
3. poolmon(P + B):定位到具体 Tag
        ↓ 交叉验证多个 Tag 是否对齐
4. 用户态句柄排查:排除或确认
        ↓ 若全部正常 → 内核层
5. 计算泄漏速率:判断紧急程度 + 决定观察时长
        ↓
6. WMI 事件订阅:抓进程创建源头
        ↓
7. 追父进程 → 抓完整命令行
        ↓
8. 定位根因 → 修复 → 验证 → 重启 → 次日复查

几条经验

  1. "占用高"不等于"内存不够"。Standby、Metafile、buff/cache 都是可回收的,先看 可用 而非 已使用
  2. 进程总和与系统已用的差额,是最有价值的单一指标。它直接决定往哪一层挖。
  3. 多个 Tag 相互印证比单个 Tag 可靠得多Proc / MiP2 / Toke 三个数字对齐,基本排除了误读的可能。
  4. 算数量级。页表理论值 80 MB vs 实测 5.79 GB;16 万 ÷ 28 小时 = 5668/小时;5.79 GB ÷ 16 万 = 37 KB。每一次除法都在收敛范围。
  5. 阴性结果同样是信息。用户态句柄正常,恰恰证明了问题在内核。
  6. 区分"导火索"和"放大器"。高频创建是导火索,不释放引用是放大器。只修一个可能只是把问题变慢,不是解决。
  7. 修复后必须做长周期复查。即时验证只能证明"止血成功",证明不了"没有基础病"。

附录:命令速查表

内存概览

powershell

# 进程内存 Top 15
Get-Process | Sort WS -Desc | Select -First 15 Name,Id,@{n='MB';e={[int]($_.WorkingSet64/1MB)}} | ft -AutoSize

# 所有进程内存总和(GB)
[math]::Round((Get-Process | Measure-Object WS -Sum).Sum/1GB, 2)

# 句柄数 Top 15
Get-Process | Sort HandleCount -Desc | Select -First 15 Name,Id,HandleCount | ft -AutoSize

# 系统总句柄数
(Get-Process | Measure-Object HandleCount -Sum).Sum

开机时长与泄漏速率

powershell

$b=(Get-CimInstance Win32_OperatingSystem).LastBootUpTime; $u=(Get-Date)-$b; "开机: $b"; "已运行: $([math]::Round($u.TotalHours,1)) 小时"

进程创建监控(60 秒)

powershell

Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -SourceIdentifier PS1 | Out-Null; Start-Sleep 60; $e=Get-Event -SourceIdentifier PS1 -EA 0; "60秒共创建 $($e.Count) 个进程"; $e | %{ $_.SourceEventArgs.NewEvent.ProcessName } | Group -NoElement | Sort Count -Desc | Select -First 12 | ft Count,Name -AutoSize; Unregister-Event -SourceIdentifier PS1; Get-Event -SourceIdentifier PS1 -EA 0 | Remove-Event

进程创建监控(含父进程)

powershell

Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -SourceIdentifier PS2 | Out-Null; Start-Sleep 30; $e=Get-Event -SourceIdentifier PS2 -EA 0; $e | %{ $n=$_.SourceEventArgs.NewEvent; $p=(Get-Process -Id $n.ParentProcessID -EA 0).Name; "{0}  <-  {1} (PID {2})" -f $n.ProcessName,$p,$n.ParentProcessID } | Group -NoElement | Sort Count -Desc | Select -First 12 | ft Count,Name -AutoSize; Unregister-Event -SourceIdentifier PS2; Get-Event -SourceIdentifier PS2 -EA 0 | Remove-Event

抓取指定进程的完整命令行

powershell

1..40 | %{ Get-CimInstance Win32_Process -Filter "Name='git.exe'" -EA 0 | select -Exp CommandLine; Start-Sleep -m 500 } | Group -NoElement | Sort Count -Desc | Select -First 5 | ft -AutoSize

非微软签名驱动清单

powershell

Get-CimInstance Win32_SystemDriver | ? State -eq 'Running' | % { $p=$_.PathName -replace '^\\\?\?\\',''; $s=Get-AuthenticodeSignature $p -EA 0; if ($s.SignerCertificate.Subject -and $s.SignerCertificate.Subject -notmatch 'Microsoft') { [PSCustomObject]@{ 驱动=$_.Name; 厂商=($s.SignerCertificate.Subject -split ',')[0] -replace 'CN=' } } } | Sort 厂商 | ft -AutoSize

Git 相关检查

powershell

Test-Path "$env:USERPROFILE\.git"          # 主目录是否被 init 成仓库
where.exe git                              # 确认 git 来源(避免 conda 版本)
git -C $env:USERPROFILE log --oneline -10  # 查看仓库历史
git -C $env:USERPROFILE remote -v          # 是否有远端备份

工具下载

工具 用途 来源
RAMMap 内存构成分析 SysInternals
poolmon 内核池 Tag 分析 Windows Driver Kit
Process Explorer 句柄/进程树查看 SysInternals

总结

内容
表象 32G 内存占用 93%,可用仅 2.3G
误导 所有进程加起来只有 6.14 GB,任务管理器完全看不出问题
真相 16 万个僵尸进程对象,占用页表 5.79G + 内核池 4.5G ≈ 10 GB
导火索 ChatGPT 桌面版每秒调用 git 扫描整个用户主目录,超时后 taskkill /F 强杀
根因 两年前在 C:\Users\<用户名> 误执行 git init,git 向上查找机制导致扫描范围膨胀到 50 万文件
放大器 疑似内核驱动持有进程对象引用不释放(Frees = 2)
解法 重命名主目录 .git → 验证 git.exe 归零 → 重启回收内存 → 次日复查 poolmon
效果 60 秒进程创建数 150 → 2,git.exetaskkill.exe 双双归零

最大的收获不是"删掉主目录的 .git"这个结论,而是那条分层排查路径——当任务管理器帮不上忙时,RAMMap 告诉你内存在哪一层,poolmon 告诉你是哪类对象,WMI 事件告诉你是谁在制造它们。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐