Skip to content

垃圾回收机制

第一部分:垃圾回收基础

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. 开发流程
   ✅ 代码审查
   ✅ 性能测试
   ✅ 监控告警
   ✅ 持续改进

理解垃圾回收机制能帮助编写更高效的代码,但要记住:清晰的代码比过度优化的代码更重要。