本节摘要:Electron 应用的内存问题要从进程归因开始(主进程膨胀与渲染层泄漏的处方完全不同),核心工具是 DevTools 的堆快照三连拍对比法。本节讲完整的泄漏诊断流程、四类典型泄漏形态(事件监听器、闭包缓存、游离 DOM、定时器与订阅)的机理与修复模式,并以一次"越用越卡"的实际排查过程贯穿。
用户说"你的应用吃内存",工程师的第一反应不该是打开代码,而是打开任务管理器。回忆 2.1 节的进程树:主进程、若干渲染进程、GPU 进程——先看是谁在涨,四种涨法的处方各不相同:
主进程持续上涨,多半是主进程侧的缓存无上限、事件监听器堆积(每个窗口关闭时没清理)、或 IPC 消息对象被长期引用。渲染进程持续上涨,最常见的是页面级泄漏(监听器、闭包、DOM 引用),DevTools 直接可查。GPU 进程异常,通常指向合成层滥用(5.3 节)。全部进程一起温和上涨再不回落,也可能是 Chromium 的正常行为(预分配与缓存),要与"用户级卡顿"分开评估。

DevTools 的 Memory 面板是主场。流程要领:操作轮数要够多(泄漏一两个对象看不出来,做二十轮让增量显著);对比视图优于单拍(看 Summary 切到 Comparison,找"只增不减"的构造器);保留树向上追(谁引用着它,谁就是泄漏源)。
面板操作流(DevTools → Memory → Heap snapshot): 1. 打开疑似页面,先手动触发一次垃圾回收,拍【快照A·基准】 2. 执行泄漏嫌疑操作 20 轮(如打开关闭某面板 20 次) 3. 再触发垃圾回收,拍【快照B】 4. 又 20 轮操作 + 回收,拍【快照C】 5. 选 C 与 B 的对比:对象数仍在净增的构造器 → 嫌疑人 6. 点开嫌疑人 → Retainers 面板向上追引用 → 回到代码修
监听器未清理。组件销毁时没把挂到全局对象、window、或 IPC 通道上的监听器摘掉,回调连同它捕获的整个作用域一起被钉在内存里。机理是引用链:监听器持有回调、回调持有闭包、闭包持有组件数据。修法是生命周期配对——注册与反注册成对出现(2.3 节桥接口的"返回反注册函数"正是为此预留的设计)。
无上限缓存。用普通对象或数组当缓存,只进不出。修法二选一:换成有界的 LRU 缓存(超容量自动淘汰最久未用项);或按会话清空(窗口关闭、页面切换时整包丢弃)。
游离 DOM。节点已从页面移除,但 JS 变量还引用着它,整个子树都无法回收。典型出处在"存了一堆节点引用的列表"。修法:引用改存数据(ID 或对象),要用时按需查询 DOM。
定时器与轮询。setInterval 与递归 setTimeout 被遗忘后永久跳动,既耗 CPU 又钉住闭包。修法:集中登记、统一清理,页面卸载前全部停掉。
// 一个把四类修法揉在一起的生命周期管理器(示意) const disposables = []; // 本页所有需要清理的资源统一登记 function setupFeature() { const timer = setInterval(pollStatus, 5000); disposables.push(() => clearInterval(timer)); const off = bridge.onStatusChanged(updateUI); // 桥接口返回反注册函数 disposables.push(off); } window.addEventListener('beforeunload', () => { disposables.splice(0).forEach(dispose => dispose()); // 成对清理,一个不落 });
背景:一个聊天应用桌面版,客服团队反馈"开一个班次(约八小时)后明显变卡,重启即恢复"。任务管理器显示渲染进程从两百兆涨到一点二吉字节。
操作:按流程走——先归因(渲染进程,主进程平稳);让客服配合复现:三连拍快照期间反复"打开关闭历史消息面板"。对比快照发现消息组件的构造器对象数每轮操作净增十几个,保留树显示被一个全局的消息缓存 Map 引用。读代码定位:历史面板每次打开都往缓存塞新拉取的消息对象,从不淘汰,且每个对象还带着渲染过的节点引用(游离 DOM 叠加)。
结果:缓存改为按会话 LRU(上限两千条),节点引用改为只存消息 ID。班次末内存稳定在四百兆附近,卡顿消失。
解读:这是"无上限缓存 + 游离 DOM"的复合泄漏,也说明复现脚本要贴近真实使用模式——实验室里"打开应用放着"永远复现不了八小时班次的泄漏。 机制化之外还有个组织层面的技巧同样重要:把"泄漏四形态"做成评审时的对照卡,任何涉及监听器注册、缓存写入、节点引用、定时器的新代码,评审人都按卡过一遍。内存问题最狡猾之处在于它从不当场报错——代码合入时一切正常,两周后才以"越用越卡"的形式浮出水面,到时候连回归到哪个提交都难定位;前置对照卡的五分钟,买的是两周后的排查工时。变式:若是主进程侧的同类问题(比如消息全部镜像进主进程做索引),工具换成主进程调试器的堆快照,流程完全一致,只是拍摄入口不同。
还有一类"假泄漏"值得单独点名:缓存类对象的增长是有意的、有上限在控制中的,堆快照里它同样表现为数量净增,容易误判。区分的方法是看增长是否有界——把操作轮数翻倍再看一次,有界缓存的增长会显著放缓,真泄漏则继续线性攀升。给团队省下"追查一个正常缓存"的半天,这个对照实验功不可没。
内存稳住之后,下一节解决第一印象问题:冷启动那几秒钟,都花在哪了。