Js
垃圾回收机制
JavaScript 的垃圾回收机制是什么?
核心答案
**垃圾回收(Garbage Collection,GC)**是 JavaScript 引擎自动管理内存的机制,它会自动找出不再使用的变量并释放其占用的内存。
垃圾回收的基本概念
目的:自动管理内存,防止内存泄漏。
原理:找出不再被引用的对象,释放其内存。
特点:
- 自动执行:开发者无需手动管理
- 周期性执行:定期清理不再使用的内存
- 影响性能:GC 执行时会暂停代码执行(Stop-The-World)
垃圾回收算法
1. 标记清除(Mark-and-Sweep)
最常用的算法,分为两个阶段:
1. 标记阶段(Mark)
- 从根对象(全局对象、调用栈)开始
- 标记所有可达的对象
- 遍历对象的引用,标记被引用的对象
2. 清除阶段(Sweep)
- 遍历所有对象
- 清除未标记的对象
- 释放其内存
示例:
let obj1 = { name: 'obj1' };
let obj2 = { name: 'obj2' };
obj1.ref = obj2;
obj2.ref = obj1;
obj1 = null;
obj2 = null;
// 标记清除:
// 1. 从根对象开始,找不到 obj1 和 obj2 的引用
// 2. 标记它们为不可达
// 3. 清除它们(即使它们互相引用)优点:
- 可以处理循环引用
- 算法简单
缺点:
- 可能产生内存碎片
- 需要遍历所有对象
2. 引用计数(Reference Counting)
原理:记录每个对象被引用的次数,引用数为 0 时回收。
示例:
let obj1 = { name: 'obj1' }; // 引用计数:1
let obj2 = obj1; // 引用计数:2
obj1 = null; // 引用计数:1
obj2 = null; // 引用计数:0,回收问题:无法处理循环引用
let obj1 = { name: 'obj1' };
let obj2 = { name: 'obj2' };
obj1.ref = obj2; // obj2 引用计数:2
obj2.ref = obj1; // obj1 引用计数:2
obj1 = null; // obj1 引用计数:1(obj2.ref 仍引用)
obj2 = null; // obj2 引用计数:1(obj1.ref 仍引用)
// 无法回收,导致内存泄漏现代浏览器:不再使用引用计数,改用标记清除。
3. 分代回收(Generational Collection)
V8 引擎使用,将对象分为两代:
新生代(Young Generation)
- 新创建的对象
- 生命周期短
- 使用 Scavenge 算法(复制算法)
- 回收频繁,速度快
老生代(Old Generation)
- 存活时间长的对象
- 从新生代晋升而来
- 使用标记清除 + 标记整理
- 回收较少,但耗时较长
晋升条件:
- 对象在新生代中存活时间超过阈值
- 新生代空间不足时,部分对象晋升
内存泄漏的常见原因
1. 全局变量
// 不推荐:创建全局变量
function createData() {
data = new Array(1000000).fill('data'); // 没有 var/let/const
}
// 推荐:使用局部变量
function createData() {
const data = new Array(1000000).fill('data');
}2. 闭包
// 问题:闭包持有大对象的引用
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function() {
// 即使不使用 largeData,闭包也会持有引用
console.log('Handler');
};
}
// 解决:使用完后清理
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log('Handler');
largeData = null; // 清理引用
};
}3. 事件监听器未移除
// 问题:事件监听器未移除
element.addEventListener('click', handler);
// 解决:组件卸载时移除
useEffect(() => {
element.addEventListener('click', handler);
return () => {
element.removeEventListener('click', handler);
};
}, []);4. 定时器未清除
// 问题:定时器未清除
const timer = setInterval(() => {
console.log('tick');
}, 1000);
// 解决:清除定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);5. DOM 引用
// 问题:DOM 引用未清理
const elements = document.querySelectorAll('.item');
// 解决:使用完后清理
const elements = document.querySelectorAll('.item');
// 使用 elements
elements = null; // 清理引用延伸追问
1. 如何检测内存泄漏?
回答:检测方法:
1. Chrome DevTools Memory
- 使用 Heap Snapshot
- 对比多个快照
- 查找未释放的对象
2. Performance Monitor
- 监控内存使用
- 查看内存趋势
- 发现内存泄漏
3. 代码检查
- 检查全局变量
- 检查事件监听器
- 检查定时器
- 检查闭包
4. 使用工具
// 监控内存使用
setInterval(() => {
const memory = performance.memory;
console.log({
used: memory.usedJSHeapSize,
total: memory.totalJSHeapSize,
limit: memory.jsHeapSizeLimit
});
}, 1000);2. 如何优化内存使用?
回答:优化方法:
1. 及时清理引用
// 使用完后设置为 null
let largeData = new Array(1000000).fill('data');
// 使用 largeData
largeData = null; // 清理引用2. 避免全局变量
// 不推荐
window.data = largeData;
// 推荐:使用模块作用域
const data = largeData;3. 使用 WeakMap/WeakSet
// WeakMap 的键是弱引用,不影响垃圾回收
const weakMap = new WeakMap();
weakMap.set(obj, data);
// obj 被回收时,weakMap 中的条目也会被回收4. 分批处理大数据
// 不推荐:一次性处理大量数据
const results = largeArray.map(process);
// 推荐:分批处理
function processBatch(array, batchSize = 1000) {
for (let i = 0; i < array.length; i += batchSize) {
const batch = array.slice(i, i + batchSize);
batch.forEach(process);
// 每批处理完后,可以触发 GC
}
}3. V8 引擎的垃圾回收策略?
回答:V8 的策略:
1. 分代回收
- 新生代:使用 Scavenge 算法
- 老生代:使用标记清除 + 标记整理
2. 增量标记(Incremental Marking)
- 将标记过程分成多个小步骤
- 与代码执行交替进行
- 减少暂停时间
3. 并发标记(Concurrent Marking)
- 在后台线程进行标记
- 不阻塞主线程
- 提高性能
4. 空闲时回收(Idle-Time GC)
- 利用空闲时间进行 GC
- 减少对用户交互的影响
4. 如何手动触发垃圾回收?
回答:方法:
1. Chrome DevTools
- 打开 DevTools
- Memory 面板
- 点击"Collect garbage"按钮
2. 代码触发(不推荐)
// 仅在开发环境使用
if (global.gc) {
global.gc();
}3. 内存压力
// 创建大量对象,触发 GC
for (let i = 0; i < 1000000; i++) {
new Array(1000).fill(0);
}注意:通常不需要手动触发,让引擎自动管理即可。
5. WeakMap 和 WeakSet 的作用?
回答:作用:
WeakMap:
- 键必须是对象
- 键是弱引用,不影响垃圾回收
- 适合存储对象的元数据
const weakMap = new WeakMap();
const obj = {};
weakMap.set(obj, 'metadata');
// obj 被回收时,weakMap 中的条目也会被回收WeakSet:
- 值必须是对象
- 值是弱引用
- 适合存储对象集合
const weakSet = new WeakSet();
const obj = {};
weakSet.add(obj);
// obj 被回收时,weakSet 中的条目也会被回收应用场景:
- DOM 节点关联数据
- 对象缓存
- 避免内存泄漏
(注:文档部分内容可能由 AI 生成)