Appearance
垃圾回收机制
第一部分:垃圾回收基础
1.1 什么是垃圾回收
垃圾回收(Garbage Collection, GC)是JavaScript引擎自动管理内存的机制,负责回收不再使用的对象所占用的内存空间,防止内存泄漏。
核心概念:
javascript
// 可达性(Reachability)
// 对象是"可达的"如果它可以被访问到
// 1. 根对象(Roots)
// - 全局对象(window)
// - 当前执行的函数及其变量
// - 调用栈中的所有变量
// 2. 引用链
let obj = { value: 42 }; // obj是根的一部分,可达
let ref = obj; // ref引用obj,obj仍可达
obj = null; // obj不再是根
// 但ref仍引用对象,对象仍可达
ref = null; // 没有引用了
// 对象不可达,可以被GC
// 示例
function example() {
let a = { name: 'A' };
let b = { name: 'B' };
let c = { name: 'C' };
a.ref = b; // A → B
b.ref = c; // B → C
c.ref = a; // C → A(循环引用)
// 此时所有对象可达
a = null;
b = null;
c = null;
// 没有外部引用了
// 整个环都不可达
// 可以被GC(标记清除算法)
}1.2 V8引擎的GC策略
javascript
// V8使用分代式GC
// 1. 新生代(New Space)
// - 大小:1-8MB
// - 对象:新创建的对象
// - 算法:Scavenge(复制算法)
// - 频率:频繁(Minor GC)
// 2. 老生代(Old Space)
// - 大小:较大
// - 对象:长期存活的对象
// - 算法:Mark-Sweep, Mark-Compact
// - 频率:较低(Major GC)
// 对象晋升
// 新生代对象经过两次Minor GC后晋升到老生代
// 示例:观察GC
function observeGC() {
if (performance.memory) {
const initial = performance.memory.usedJSHeapSize;
// 创建大量临时对象
for (let i = 0; i < 1000000; i++) {
const temp = { value: i };
}
// 对象失去引用,等待GC
setTimeout(() => {
const current = performance.memory.usedJSHeapSize;
const diff = current - initial;
console.log('Memory change:', (diff / 1024 / 1024).toFixed(2), 'MB');
}, 1000);
}
}
// GC触发时机
// - 新生代空间不足
// - 老生代空间不足
// - 手动触发(--expose-gc)
// 手动GC(仅开发环境,需要--expose-gc标志)
if (window.gc) {
window.gc();
}1.3 GC算法
javascript
// 标记清除(Mark-Sweep)
/*
标记阶段:
1. 从根对象开始遍历
2. 标记所有可达对象
3. 未被标记的对象即为垃圾
清除阶段:
1. 遍历堆
2. 回收未标记的对象
3. 更新内存指针
*/
// 示例
let root = {
a: { value: 1 },
b: { value: 2 }
};
let temp = root.a;
root.a = null;
// root.a被设为null
// 但temp仍引用{ value: 1 }
// 对象仍可达,不会被回收
temp = null;
// 现在{ value: 1 }不可达,会被GC
// 引用计数(已废弃)
/*
问题:无法处理循环引用
let a = { name: 'A' };
let b = { name: 'B' };
a.ref = b; // b引用计数+1
b.ref = a; // a引用计数+1
a = null; // a引用计数-1,但仍为1
b = null; // b引用计数-1,但仍为1
// 循环引用,引用计数永不为0
// 内存泄漏!
*/
// 增量标记(Incremental Marking)
/*
问题:大堆的标记会阻塞主线程
解决:
1. 标记过程分成多个小步骤
2. 与JavaScript执行交替进行
3. 减少停顿时间
*/
// 并发标记(Concurrent Marking)
/*
V8 6.0+引入
- 标记在后台线程进行
- 不阻塞JavaScript执行
- 显著减少停顿
*/1.4 GC性能影响
javascript
// GC暂停时间
/*
Minor GC: 1-10ms
Major GC: 10-100ms(可能更长)
*/
// 观察GC暂停
performance.mark('operation-start');
// 创建大量对象
const objects = [];
for (let i = 0; i < 1000000; i++) {
objects.push({ index: i, data: new Array(100).fill(i) });
}
performance.mark('operation-end');
performance.measure('operation', 'operation-start', 'operation-end');
const duration = performance.getEntriesByName('operation')[0].duration;
console.log('Operation time:', duration, 'ms');
// 如果duration明显超过预期,可能触发了GC
// 监控GC频率
function monitorGC() {
const samples = [];
let lastMemory = performance.memory?.usedJSHeapSize || 0;
setInterval(() => {
if (!performance.memory) return;
const currentMemory = performance.memory.usedJSHeapSize;
// 内存突然大幅下降,可能是GC
if (lastMemory - currentMemory > 1024 * 1024) { // 下降超过1MB
samples.push({
type: 'gc-detected',
freed: (lastMemory - currentMemory) / 1024 / 1024,
timestamp: Date.now()
});
console.log('Possible GC, freed:', (lastMemory - currentMemory) / 1024 / 1024, 'MB');
}
lastMemory = currentMemory;
}, 100);
}第二部分:优化GC性能
2.1 减少对象创建
javascript
// ❌ 频繁创建对象
function BadObjectCreation() {
const handleClick = () => {
for (let i = 0; i < 10000; i++) {
const temp = { value: i }; // 大量临时对象
process(temp);
}
};
return <button onClick={handleClick}>Click</button>;
}
// ✅ 复用对象
function GoodObjectCreation() {
const temp = useRef({ value: 0 });
const handleClick = () => {
for (let i = 0; i < 10000; i++) {
temp.current.value = i; // 复用对象
process(temp.current);
}
};
return <button onClick={handleClick}>Click</button>;
}
// ❌ 渲染中创建对象
function BadRenderObjects() {
const [count, setCount] = useState(0);
return (
<div>
{/* 每次渲染都创建新对象 */}
<Child config={{ theme: 'dark', count }} />
</div>
);
}
// ✅ 使用useMemo
function GoodRenderObjects() {
const [count, setCount] = useState(0);
const config = useMemo(() => ({
theme: 'dark',
count
}), [count]);
return (
<div>
<Child config={config} />
</div>
);
}
// ❌ 循环中创建函数
function BadLoopFunctions({ items }) {
return (
<div>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item.id)}>
{/* 每次渲染创建新函数 */}
Click
</button>
))}
</div>
);
}
// ✅ 复用函数
function GoodLoopFunctions({ items }) {
const handleClick = useCallback((id) => {
console.log('clicked', id);
}, []);
return (
<div>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item.id)}>
Click
</button>
))}
</div>
);
}2.2 对象池模式
javascript
// 对象池减少GC压力
class ObjectPool {
constructor(factory, reset, initialSize = 10) {
this.factory = factory;
this.reset = reset;
this.pool = [];
// 预创建对象
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory());
}
}
acquire() {
return this.pool.pop() || this.factory();
}
release(obj) {
this.reset(obj);
this.pool.push(obj);
}
}
// 使用
const particlePool = new ObjectPool(
() => ({ x: 0, y: 0, vx: 0, vy: 0 }), // factory
(obj) => { // reset
obj.x = 0;
obj.y = 0;
obj.vx = 0;
obj.vy = 0;
},
100 // 初始大小
);
function ParticleSystem() {
const [particles, setParticles] = useState([]);
const addParticle = () => {
const particle = particlePool.acquire();
particle.x = Math.random() * 100;
particle.y = Math.random() * 100;
setParticles(prev => [...prev, particle]);
};
const removeParticle = (particle) => {
setParticles(prev => prev.filter(p => p !== particle));
particlePool.release(particle); // 归还到池
};
return (
<div>
<button onClick={addParticle}>Add Particle</button>
{particles.map((p, i) => (
<Particle key={i} data={p} onRemove={() => removeParticle(p)} />
))}
</div>
);
}2.3 避免GC停顿
javascript
// ❌ 一次性创建大量对象
function BadBatchCreate() {
const handleClick = () => {
const items = [];
for (let i = 0; i < 1000000; i++) {
items.push({ id: i, value: Math.random() });
}
setItems(items);
// 大量对象创建,触发GC,可能暂停
};
}
// ✅ 分批创建
function GoodBatchCreate() {
const handleClick = () => {
const batchSize = 10000;
let currentBatch = 0;
const createBatch = () => {
const items = [];
for (let i = 0; i < batchSize; i++) {
items.push({
id: currentBatch * batchSize + i,
value: Math.random()
});
}
setItems(prev => [...prev, ...items]);
currentBatch++;
if (currentBatch < 100) {
requestIdleCallback(createBatch); // 空闲时继续
}
};
createBatch();
};
}
// ❌ 大对象的频繁创建销毁
function BadLargeObjects() {
useEffect(() => {
const interval = setInterval(() => {
const largeArray = new Array(100000).fill(0);
process(largeArray); // 处理后失去引用
}, 100); // 频繁创建大对象
return () => clearInterval(interval);
}, []);
}
// ✅ 复用大对象
function GoodLargeObjects() {
const largeArrayRef = useRef(new Array(100000).fill(0));
useEffect(() => {
const interval = setInterval(() => {
process(largeArrayRef.current); // 复用对象
}, 100);
return () => clearInterval(interval);
}, []);
}第三部分:React与GC
3.1 React渲染与GC
javascript
// React的虚拟DOM和GC
function ReactGCInteraction() {
const [items, setItems] = useState([]);
// ❌ 每次渲染创建新对象
return (
<ul>
{items.map(item => (
<li key={item.id} style={{ color: 'red' }}>
{/* 每次渲染style对象都是新的 */}
{item.text}
</li>
))}
</ul>
);
}
// ✅ 复用对象
const liStyle = { color: 'red' };
function OptimizedReactGC() {
const [items, setItems] = useState([]);
return (
<ul>
{items.map(item => (
<li key={item.id} style={liStyle}>
{item.text}
</li>
))}
</ul>
);
}
// Fiber和GC
// React Fiber的增量渲染可以与GC配合
// - 工作单元小,GC可在间隙执行
// - 减少长时间停顿
// 组件卸载和GC
const MountedComponents = new Set();
function TrackableComponent({ id }) {
useEffect(() => {
MountedComponents.add(id);
console.log('Mounted components:', MountedComponents.size);
return () => {
MountedComponents.delete(id);
console.log('Mounted components after cleanup:', MountedComponents.size);
};
}, [id]);
return <div>Component {id}</div>;
}3.2 优化GC友好的代码
javascript
// 1. 使用原始类型
// ✅ 原始类型(不占堆内存)
const count = 42;
const name = 'John';
const active = true;
// ❌ 不必要的对象包装
const count = { value: 42 };
const name = { text: 'John' };
// 2. 避免意外的全局变量
// ❌ 隐式全局变量
function bad() {
implicitGlobal = 'value'; // 没有var/let/const
}
// ✅ 显式声明
function good() {
const explicitLocal = 'value';
}
// 3. 及时释放大对象
function handleLargeData() {
let largeData = loadLargeData();
processData(largeData);
largeData = null; // 及时释放
}
// 4. 使用局部变量
// ❌ 全局缓存
const globalCache = {};
function bad(key, value) {
globalCache[key] = value; // 永不释放
}
// ✅ 局部缓存 + 清理
function good() {
const localCache = new Map();
useEffect(() => {
return () => {
localCache.clear(); // 组件卸载时清理
};
}, []);
return { cache: localCache };
}
// 5. 避免闭包陷阱
// ❌ 闭包捕获大对象
function badClosure() {
const largeObject = createLargeObject();
return () => {
console.log(largeObject.id); // 只需要id
// 但整个largeObject无法被GC
};
}
// ✅ 只保留需要的值
function goodClosure() {
const largeObject = createLargeObject();
const id = largeObject.id;
return () => {
console.log(id); // 只引用id
// largeObject可以被GC
};
}3.3 WeakMap和WeakSet
javascript
// WeakMap:键的弱引用
function useWeakMapCache() {
const cache = useRef(new WeakMap());
const getCachedData = (obj) => {
if (cache.current.has(obj)) {
return cache.current.get(obj);
}
const data = computeExpensiveData(obj);
cache.current.set(obj, data);
return data;
};
// obj被GC时,WeakMap中的条目自动删除
return getCachedData;
}
// WeakSet:值的弱引用
function useProcessedTracker() {
const processed = useRef(new WeakSet());
const markProcessed = (obj) => {
processed.current.add(obj);
};
const isProcessed = (obj) => {
return processed.current.has(obj);
};
return { markProcessed, isProcessed };
}
// 对比Map和WeakMap
function CompareMapWeakMap() {
// Map:强引用
const mapCache = new Map();
let obj1 = { id: 1 };
mapCache.set(obj1, 'data');
obj1 = null; // obj仍被mapCache引用,无法GC
// WeakMap:弱引用
const weakMapCache = new WeakMap();
let obj2 = { id: 2 };
weakMapCache.set(obj2, 'data');
obj2 = null; // obj可以被GC,weakMapCache中的条目自动删除
}
// 实际应用:DOM元素关联数据
const domDataCache = new WeakMap();
function attachDataToElement(element, data) {
domDataCache.set(element, data);
}
function getElementData(element) {
return domDataCache.get(element);
}
// element被移除后,关联数据自动被GC注意事项
1. 不要过度优化
javascript
// GC优化要适度
// ❌ 过度优化
function OverOptimized() {
// 为了避免GC,复用所有对象
const reusableObj = useRef({});
// 代码变得复杂且难以维护
}
// ✅ 合理优化
function ReasonableOptimization() {
// 只优化性能关键路径
// 简单场景让GC自动处理
}2. 测量实际影响
javascript
// 先测量,再优化
function measureGCImpact() {
const before = performance.now();
// 执行操作
performOperation();
const after = performance.now();
const duration = after - before;
console.log('Operation took:', duration, 'ms');
// 如果duration合理,GC影响可忽略
}3. 避免过早优化
javascript
// 1. 先编写可读代码
// 2. 性能测试
// 3. 如果有问题,再优化
// 4. 验证优化效果
// 不要为了GC牺牲代码可读性常见问题
Q1: 如何知道GC何时发生?
A: 监控内存使用变化,或使用--trace-gc标志。
Q2: 可以手动触发GC吗?
A: 生产环境不可以,开发环境需--expose-gc。
Q3: 频繁GC是坏事吗?
A: 不一定,Minor GC很快,Major GC频繁才是问题。
Q4: WeakMap什么时候用?
A: 需要关联数据到对象,且对象可能被GC时。
Q5: 对象池值得用吗?
A: 高频创建销毁场景(如游戏、动画)值得。
Q6: React组件会影响GC吗?
A: 会,组件创建销毁会产生对象,需合理优化。
Q7: useMemo会增加GC压力吗?
A: 会轻微增加内存使用,但减少重复计算。
Q8: 如何平衡GC和性能?
A: 测量实际影响,避免过度优化。
Q9: 闭包对GC有什么影响?
A: 闭包会延长变量生命周期,注意大对象引用。
Q10: React 19的GC特性?
A: 编译器优化可能减少临时对象创建。
总结
核心要点
1. GC原理
✅ 标记清除算法
✅ 分代回收
✅ 增量/并发标记
✅ 可达性分析
2. GC优化
✅ 减少对象创建
✅ 复用对象
✅ 及时释放引用
✅ 使用WeakMap
3. React优化
✅ useMemo/useCallback
✅ 对象池
✅ 避免闭包陷阱
✅ 合理的组件设计最佳实践
1. 编码习惯
✅ 避免全局变量
✅ 及时释放引用
✅ 使用局部变量
✅ 注意闭包
2. 性能优化
✅ 测量后优化
✅ 关键路径优化
✅ 避免过度优化
✅ 验证效果
3. 开发流程
✅ 代码审查
✅ 性能测试
✅ 监控告警
✅ 持续改进理解垃圾回收机制能帮助编写更高效的代码,但要记住:清晰的代码比过度优化的代码更重要。