一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何理解内存管理中“标记清除”算法的工作流程及如何预防泄漏

时间:2026-07-19 10:57:55 编辑:袖梨 来源:一聚教程网

标记清除算法只回收不可达对象,泄漏与否取决于代码是否使本该释放的对象保持可达;标记阶段从根对象(如全局对象、栈变量、DOM引用、定时器等)出发递归标记所有可达对象。

标记清除算法本身不预防泄漏,它只负责回收“不可达”对象;泄漏是否发生,取决于你写的代码有没有让本该释放的对象一直保持可达。

标记阶段:从哪些地方开始找“活对象”?

标记不是凭空发生的,它依赖一组明确的根对象(GC Roots)。不同环境的根集合略有差异:

  • 浏览器中:全局对象(windowglobalThis)、当前执行上下文的栈帧(含函数参数、局部变量)、正在运行的定时器回调、DOM 元素引用、postMessage 消息监听器等
  • Node.js:global 对象、活跃的 setTimeout/setInterval 句柄、process 事件监听器
  • React 组件内:如果组件实例被闭包或事件监听器强引用,它就可能成为事实上的“根”,哪怕已卸载

关键点:只要一个对象能通过任意引用链从任一根对象抵达,它就会被标记为“存活”。这意味着——addEventListenersetTimeout、未清理的 Promise 回调,都可能意外延长对象生命周期。

清除阶段:为什么有些对象“明明不用了”却没被清掉?

清除只处理“未被标记”的对象。所以问题从来不在清除逻辑,而在于标记阶段把不该保活的对象也标上了。常见诱因包括:

  • document.addEventListener('scroll', handler):即使组件已 unmounthandler 仍挂在 document 上,闭包里捕获的组件实例无法被 GC
  • setInterval(() => console.log(this.data), 1000)this 被定时器长期持有,且定时器本身是全局根
  • 缓存 Map/WeakMap 使用不当:cache.set(key, hugeObject) 后忘记 cache.delete(key),key 未失效则值永驻

注意:WeakMapWeakRef 不参与标记——它们不阻止所引用对象被清除,但也不能解决“本不该被引用却一直被引用”的问题。

如何验证某个对象是否真被 GC 掉了?

不能只看内存占用曲线,得用 DevTools 直接观察对象存活状态:

  • 在 Chrome DevTools 的 Memory 面板中录制一次 Allocation instrumentation on timeline,触发疑似泄漏操作后停止,筛选“Detached DOM tree”或“JS heap”中长期增长的对象构造器名
  • console.memory 粗略判断趋势,但更可靠的是拍堆快照(Heap Snapshot),对比两次快照,按 Retainers 列查看谁在持有着目标对象
  • 在代码中插入 console.count('MyComponent mounted')console.count('MyComponent cleanup'),确认卸载逻辑确实执行了

最容易被忽略的一点:泄漏常发生在“多层间接引用”中——比如一个被遗忘的 EventTarget 实例,它内部又绑着一堆监听器,每个监听器又闭包着组件实例。这种链式持有必须逐层切断,单靠“组件卸载”不够。

热门栏目