一台 32G 内存的机器,开着 VSCode 工作两小时后风扇狂转、切窗口卡顿,任务管理器显示编辑器占用 6.8G 且只涨不跌。这篇是排查记录:用三天时间、四次重启、一架二分法,把 100 多个插件筛到最后锁定 3 个真凶,顺带洗清了 5 个被冤枉的。
结论先行
- 真凶 3 个:一个代码格式化扩展、一个 AI 补全扩展、一个主题类扩展。
- 被误伤 5 个:日常高频使用、口碑良好,一开始被怀疑但实测无辜。
- 可复用的方法:分批禁用 + 记录内存曲线,用二分法把排查轮次从十几次压到四次。
- 一个反直觉的发现:内存占用高的插件不一定是泄漏的那个,真正的泄漏特征是”只涨不跌”。
一、症状与初步判断
问题表现很有规律:
- 刚启动时编辑器占用约 480MB,正常;
- 连续编码 60 分钟后涨到 2G 左右,仍算合理;
- 两小时后突破 5G,且关闭所有文件标签页后不回落;
- 重启编辑器立刻恢复到 480MB,然后重复上述曲线。
“关闭文件后内存不释放”是典型的泄漏信号。正常的缓存会随工作区变化而增减,泄漏则是单向累积。
二、排查方法:先定基准,再做二分
第一步:建立基准线
禁用全部扩展,重启编辑器,重复同样的工作内容(编辑、保存、切分支、运行任务)两小时,记录内存曲线。
结果:从 480MB 缓慢涨到 620MB 后趋于平稳。说明编辑器本体没有问题,泄漏确实来自扩展。
第二步:确认问题复现
启用全部扩展,重复同样流程。结果:两小时后涨到 6.8G,且没有回落迹象。问题稳定复现,可以开始二分。
第三步:二分法
把 100 多个扩展随机分成两半,只启用其中一半,跑 40 分钟看内存是否突破阈值。
- 第一轮:A 组(52 个)→ 内存涨到 3.1G,问题在 A 组。
- 第二轮:A 组再分半,A1(26 个)→ 涨到 2.8G,问题在 A1。
- 第三轮:A1 再分半,A1a(13 个)→ 内存平稳,问题在 A1b(13 个)。
- 第四轮:逐个启停 A1b 中的 13 个,锁定到 3 个。
四轮定位,比”一个一个试”的十几次省了大半天。这里是关键:二分法要每次记录”内存是否突破阈值”,而不是”涨了多少”,用二元判断才能快速收敛。
三、结果:三个真凶与五个误伤
| 类型 | 扩展类型 | 2 小时内存 | 结论 |
|---|---|---|---|
| 真凶 | 代码格式化类 | 6.8G | 单独启用即可复现 |
| 真凶 | AI 补全类 | 4.2G(单独) | 与格式化插件叠加时更严重 |
| 真凶 | 主题美化类 | 3.5G(单独) | 持续重绘导致内存累积 |
| 误伤 | Vim 模拟 / Git 增强 / 路径补全 / 括号高亮 / 拼写检查 | 合计 <900MB | 稳定,可放心保留 |
值得注意的是三个真凶叠加在一起时,内存增长速度明显快于各自单独运行时之和。这说明它们之间存在相互触发——A 的格式化动作触发了 B 的分析,B 的结果又让 C 重绘。这类”合唱效应”是排查中最容易被忽略的。
四、根因:为什么是这三类
- 格式化类:每次保存都触发全文件解析,如果实现里缓存了 AST 但不释放,打开过的文件越多,累积越大。
- AI 补全类:持续监听输入、请求补全、维护上下文缓存。如果对失败请求的重试没有做退避,会在网络不稳时堆积大量未完成的任务。
- 主题美化类:监听编辑器渲染事件并修改样式。如果每次渲染都新增监听器而不移除,监听器数量会随时间线性增长。
三类问题的共同点是:都依赖”跟随用户操作持续运行”的机制,而出问题的地方都在”清理”环节。定时任务型的扩展反而不容易出事,因为它们没有持续累积的状态。
五、可复用的排查流程
- 全禁用,建立基准线(内存曲线是否平稳)。
- 全启用,确认问题复现(曲线是否单向上涨)。
- 分批二分,每轮记录”是否突破阈值”。
- 锁定候选后,逐个单独启用并跑至少 40 分钟。
- 验证叠加效应:把候选组合起来再跑一次。
- 确定后,优先找同功能的替代扩展,而不是直接不用。
六、预防建议
- 控制扩展数量:同类功能只留一个,编辑器不是插件展览馆。
- 定期清理:装过但一个月没用过的,直接禁用,用的时候再开。
- 关注更新日志:扩展出问题往往在版本更新之后,升级后出现卡顿要优先怀疑它。
- 用工作区级的扩展配置:不同项目启用不同扩展,避免把全部负担堆在一个实例上。
- 保留一个”干净模式”:临时禁用所有扩展的快捷方式,出问题时能立刻验证是不是扩展导致。
七、替代思路:别急着卸载
锁定问题扩展之后,第一反应往往是”直接卸了”。更好的做法是先找替代:
- 同功能轻量版:大多数热门扩展都有更简单的替代品,功能少一点,但资源占用小得多。
- 按需启用:有些扩展只在特定项目里需要,改成工作区级启停,就能避免全局拖累。
- 用内置能力替换:编辑器本身的功能一直在增强,有些扩展的作用已经被原生功能覆盖了。
这样做的好处是不必牺牲功能。排查的目的是把”看不见的成本”找出来,不是把顺手的东西全砍掉。
八、结论
内存泄漏排查没有捷径,但有方法。二分法把 O(n) 的试错压成了 O(log n),建立基准线避免了”凭感觉判断”的误判,而 40 分钟以上的观察窗口能过滤掉正常的缓存增长。
最后的处理结果:三个真凶里,两个换成了同类替代品,一个只在需要时才启用。内存曲线回到”两小时稳定在 900MB 以内”,机器终于不再呼呼作响了。
