垃圾回收机制(GC)
JS引擎通过 可达性 判断内存是否需要保留: 从根对象(Roots)出发,能访问到的对象标记为"存活",无法访问的即为"可回收" 这里的“根”可以理解为浏览器或 Node.js 中的全局对象,比如 window 、 globalThis ,或者正在执行的函数上下文 标记-清除(现代引擎核心) 原理 : 标记阶段:从根开始遍历,标记所有可达对象 清除阶段:回收未标记的内存 优点 :完美解决循环引用(互相引用但都不可达时,都不会被标记) V8引擎的进阶策略:分代回收 V8将内存堆分为两个区域,针对性优化: 新生代(Young Generation) 存放的是:临时变量、局部变量、短时间内用完就丢弃的对象 特点:存活时间短(局部变量、临时对象) 空间:1-20MB,小且频繁回收 算法:Scavenge(复制算法)内存分为From和To两个半区只复制存活对象到To区,清空From区速度极快(毫秒级),但浪费一半空间 晋升:存活超过2次GC的对象 → 移入老生代 老生代(Old Generation) 存放的是:全局变量、闭包变量、长期存活的对象 特点:存活时间长(全局变量、闭包、大型数组) 空间:大且回收成本高 算法:Mark-Compact(标记-整理)并发标记:后台线程标记,不阻塞主线程整理:移动存活对象,消除内存碎片清除:回收边界外内存 老生代 GC 的触发时机 触发条件1:老生代空间快满了 V8 为老生代设置了 内存阈值 ,当老生代中的对象数量超过这个阈值,就会触发一次 Major GC(全量垃圾回收) 。 触发条件2:新生代晋升失败 当新生代的对象要晋升到老生代,但老生代 空间不足 时,也会强制触发老生代 GC。 触发条件3:内存压力信号(操作系统/浏览器) 浏览器会监听系统内存状态: 操作系统内存不足时,浏览器会主动通知 V8 进行激进回收 页面切换到后台时,降低 GC 阈值,更积极地回收 Chrome 的 Tab 休眠机制:长时间不用的 Tab 会被冻结,触发 GC 内存泄漏: 内 存泄漏 :不再需要的内存,由于某种原因未被GC回收,导致内存占用持续增长 场景1:意外创建的全局变量 function leak() { // 没有 var/let/const,挂载到 window accidentalGlobal = new Array(1000000); // 函数结束,数组仍被全局引用 → 泄漏 } // 修复:使用严格模式 'use strict' 禁止隐式全局变量 场景2:被遗忘的定时器和回调 场景3:脱离DOM的引用 let cache = {}; function addElement() { const div = document.getElementById('container'); cache.div = div; // JS 持有 DOM 引用 div.remove(); // 从 DOM 树移除 // 但 cache.div 还在引用,DOM 节点无法回收 ❌ } // 修复:移除时同步清除引用 function fixedAddElement() { const div = document.getElementById('container'); cache.div = div; div.remove(); delete cache.div; // 或 cache.div = null } 场景4:闭包滥用 场景5:事件监听器未移除 场景6:Map/Set 无限增长 let userCache = new Map(); function cacheUser(id, data) { userCache.set(id, data); // 只增不减 } // 使用 WeakMap 自动回收 let userWeakCache = new WeakMap(); // 键为对象,弱引用
布莱克2026-06-01 19:31JavaScript