[1-4] 内存机制与垃圾回收 · 栈/堆、V8 GC、内存泄漏常见场景

8 阅读3分钟

所属板块:1. 数据类型与内存机制

记录日期:2026-03-xx
更新:遇到新内存问题时补充

1. 栈内存(Stack) vs 堆内存(Heap) 快速对比

方面栈内存(Stack)堆内存(Heap)
存储内容基本类型值 + 引用类型的地址指针引用类型对象的实际内容(对象、数组等)
大小较小、固定(通常几MB)较大、动态扩展
分配/释放函数调用时自动压栈,结束时弹出由垃圾回收器(GC)管理
访问速度极快(连续内存)稍慢(指针寻址)
生命周期与函数执行上下文绑定只要有引用存在就不会被回收
典型例子let a = 100; let fn = () => {}let obj = {}; let arr = [1,2,3]

记住:栈负责“快速存取 + 自动管理”,堆负责“存放大块、可变数据 + 手动(GC)回收”。

2. V8 引擎的垃圾回收机制(主要浏览器 & Node.js 用的引擎)

V8 采用分代回收策略,把对象分为:

  • 新生代(Young Generation):存活时间短的对象,大部分对象在这里被快速回收

    • 空间较小(几MB ~ 几十MB)
    • 使用 Scavenge 算法(复制算法):From 区 → To 区复制存活对象,交换角色,清空旧区
    • 回收速度非常快,但会浪费一半空间
  • 老生代(Old Generation):经过多次新生代回收仍存活的对象

    • 空间较大(几百MB ~ GB级)
    • 使用 标记-清除(Mark-Sweep) + 标记-整理(Mark-Compact)
      • 标记阶段:从根(全局变量、栈引用等)开始遍历,标记可达对象
      • 清除阶段:清除未标记的(垃圾)
      • 整理阶段(可选):把存活对象往前挪,减少碎片

额外优化:

  • 增量标记(Incremental Marking):把标记拆分成小块,避免长时间卡顿
  • 并发标记(Concurrent Marking):部分工作放到后台线程

3. 内存泄漏的 4 种最常见场景(自己项目里最容易踩)

  1. 意外创建的全局变量

    function foo() {
      bar = "意外全局变量";   // 没有 var/let/const → 挂到 window 上
    }
    

    解决:严格模式("use strict")会报错;养成声明变量习惯。

  2. 被遗忘的定时器或事件监听

    setInterval(() => {
      // 定时器里引用了 dom 或大对象,没清理
    }, 1000);
    
    // 或
    element.addEventListener('click', handler);  // 组件卸载后没 remove
    

    解决:在组件卸载时 clearInterval / removeEventListener。

  3. 脱离 DOM 的引用仍存在

    let detached = document.getElementById('box');
    document.body.removeChild(detached);  // DOM 没了,但 detached 还持有引用
    // → 整个 DOM 树无法回收
    

    解决:移除引用 detached = null;

  4. 闭包滥用导致外部变量长期被引用

    function createLeak() {
      let bigData = new Array(10000000).fill('x');
      return function() {
        console.log(bigData.length);  // 闭包一直引用 bigData
      };
    }
    const fn = createLeak();  // fn 存在一天,bigData 就泄漏一天
    

    解决:只在必要时使用闭包;用完后置 null(较少用)。

4. 快速检测内存泄漏的小技巧(Chrome DevTools)

  • Performance 面板 → 录制一段时间 → 看内存曲线是否持续上升
  • Memory 面板 → Heap Snapshot → 多次快照对比,看哪些对象持续增长
  • 关注 Retained Size 大的对象,尤其是 Detached DOM tree

5. 小结 & 日常注意

  • 基本类型几乎不会泄漏(栈自动管理)
  • 引用类型 + 闭包 + 定时器 + DOM 操作 是泄漏重灾区
  • 写完复杂组件/长生命周期函数后,习惯问自己:“这些引用会被及时释放吗?”
  • 现代框架(React/Vue)帮我们处理了很多,但自定义缓存、大数据处理仍需小心

下一篇文章会记录浅拷贝与深拷贝的实现方式,以及手写带循环引用处理的深拷贝(用 WeakMap)。

返回总目录:戳这里