Skip to content

内存泄漏识别

第一部分:内存泄漏基础

1.1 什么是内存泄漏

内存泄漏是指程序中已分配的内存无法被释放或回收,导致可用内存逐渐减少,最终可能导致应用性能下降甚至崩溃。

表现症状:

1. 页面越用越慢
2. 内存占用持续增长
3. 浏览器标签页崩溃
4. 动画和滚动卡顿
5. 垃圾回收频繁触发

常见原因:

javascript
// 1. 未清理的定时器
function LeakyTimer() {
  useEffect(() => {
    setInterval(() => {
      console.log('tick');
    }, 1000);
    
    // ❌ 缺少清理,组件卸载后定时器仍运行
  }, []);
}

// 2. 未清理的事件监听
function LeakyListener() {
  useEffect(() => {
    const handleResize = () => console.log('resized');
    window.addEventListener('resize', handleResize);
    
    // ❌ 缺少清理
  }, []);
}

// 3. 未清理的订阅
function LeakySubscription() {
  useEffect(() => {
    const subscription = observable.subscribe(data => {
      console.log(data);
    });
    
    // ❌ 缺少取消订阅
  }, []);
}

// 4. 闭包引用
function LeakyClosure() {
  const [data] = useState(largeObject);  // 大对象
  
  useEffect(() => {
    const handler = () => {
      console.log(data);  // 闭包持有data引用
    };
    
    window.addEventListener('click', handler);
    
    // ❌ data永远无法被释放
  }, []);
}

1.2 Chrome DevTools检测

javascript
// 使用Memory面板检测内存泄漏

// 步骤1:Heap Snapshot
// 1. 打开Chrome DevTools
// 2. 切换到Memory标签
// 3. 选择"Heap snapshot"
// 4. 点击"Take snapshot"
// 5. 执行可能泄漏的操作
// 6. 再次拍快照
// 7. 对比两次快照

// 步骤2:Allocation Timeline
// 1. 选择"Allocation instrumentation on timeline"
// 2. 点击"Start"
// 3. 执行操作
// 4. 点击"Stop"
// 5. 查看内存分配时间线
// 6. 识别持续增长的分配

// 步骤3:Allocation Sampling
// 1. 选择"Allocation sampling"
// 2. 点击"Start"
// 3. 执行操作一段时间
// 4. 点击"Stop"
// 5. 查看分配堆栈

// 实例:检测定时器泄漏
function detectTimerLeak() {
  // 1. 拍摄初始快照
  // 2. 进入有定时器的页面
  // 3. 拍摄第二次快照
  // 4. 离开页面
  // 5. 拍摄第三次快照
  // 6. 对比快照2和快照3
  // 如果内存没有下降,说明定时器未清理
}

// Performance监控内存
function monitorMemory() {
  if (performance.memory) {
    setInterval(() => {
      const used = performance.memory.usedJSHeapSize / (1024 * 1024);
      const total = performance.memory.totalJSHeapSize / (1024 * 1024);
      const limit = performance.memory.jsHeapSizeLimit / (1024 * 1024);
      
      console.log(`Memory: ${used.toFixed(2)}MB / ${total.toFixed(2)}MB (Limit: ${limit.toFixed(2)}MB)`);
      
      // 内存使用超过80%警告
      if (used / limit > 0.8) {
        console.warn('High memory usage!');
      }
    }, 5000);
  }
}

1.3 识别泄漏模式

javascript
// 模式1:分离的DOM节点
// 问题:DOM被移除但仍被JavaScript引用
let detachedNodes = [];

function LeakyComponent() {
  useEffect(() => {
    const div = document.createElement('div');
    detachedNodes.push(div);  // 引用DOM节点
    
    // ❌ div从未添加到文档,但被数组引用,无法被GC
  }, []);
}

// 修复
function FixedComponent() {
  useEffect(() => {
    const div = document.createElement('div');
    document.body.appendChild(div);
    
    return () => {
      document.body.removeChild(div);  // 清理DOM
      // div现在可以被GC
    };
  }, []);
}

// 模式2:事件监听器累积
let clickCount = 0;

function AccumulatingListeners() {
  useEffect(() => {
    const handler = () => clickCount++;
    
    // ❌ 每次渲染都添加新监听器
    document.addEventListener('click', handler);
  });  // 缺少依赖数组
}

// 修复
function FixedListeners() {
  useEffect(() => {
    const handler = () => clickCount++;
    
    document.addEventListener('click', handler);
    
    return () => {
      document.removeEventListener('click', handler);
    };
  }, []);  // 空依赖数组,只执行一次
}

// 模式3:闭包陷阱
function ClosureLeak() {
  const [items, setItems] = useState([]);
  
  useEffect(() => {
    const interval = setInterval(() => {
      // 闭包捕获items的初始值
      console.log('Items count:', items.length);
      
      // ❌ items的旧版本无法被释放
    }, 1000);
    
    return () => clearInterval(interval);
  }, []);  // 缺少items依赖
}

// 修复
function FixedClosure() {
  const itemsRef = useRef([]);
  
  useEffect(() => {
    itemsRef.current = items;
  });
  
  useEffect(() => {
    const interval = setInterval(() => {
      console.log('Items count:', itemsRef.current.length);
    }, 1000);
    
    return () => clearInterval(interval);
  }, []);
}

// 模式4:全局变量累积
let globalCache = [];

function GlobalAccumulation() {
  useEffect(() => {
    const data = fetchData();
    globalCache.push(data);  // ❌ 持续累积
  }, []);
}

// 修复:限制大小
const MAX_CACHE_SIZE = 100;
let globalCache = [];

function FixedGlobal() {
  useEffect(() => {
    const data = fetchData();
    globalCache.push(data);
    
    if (globalCache.length > MAX_CACHE_SIZE) {
      globalCache.shift();  // 移除最老的
    }
  }, []);
}

第二部分:检测工具

2.1 Chrome Performance Monitor

javascript
// 打开Performance Monitor
// 1. Chrome DevTools → More tools → Performance monitor
// 2. 观察指标:
//    - JS heap size(持续增长=泄漏)
//    - DOM Nodes(持续增长=DOM泄漏)
//    - JS event listeners(持续增长=监听器泄漏)
//    - CPU usage(异常高=性能问题)

// 编程监控
function useMemoryMonitor() {
  const [memoryInfo, setMemoryInfo] = useState(null);
  
  useEffect(() => {
    const interval = setInterval(() => {
      if (performance.memory) {
        const info = {
          used: performance.memory.usedJSHeapSize,
          total: performance.memory.totalJSHeapSize,
          limit: performance.memory.jsHeapSizeLimit,
          timestamp: Date.now()
        };
        
        setMemoryInfo(info);
        
        // 检测泄漏
        const usagePercent = (info.used / info.limit) * 100;
        if (usagePercent > 90) {
          console.error('Critical memory usage:', usagePercent.toFixed(2), '%');
        }
      }
    }, 1000);
    
    return () => clearInterval(interval);
  }, []);
  
  return memoryInfo;
}

// 内存增长检测
function detectMemoryGrowth() {
  const samples = [];
  const SAMPLE_COUNT = 10;
  const GROWTH_THRESHOLD = 1.5;  // 50%增长
  
  const interval = setInterval(() => {
    if (!performance.memory) return;
    
    samples.push(performance.memory.usedJSHeapSize);
    
    if (samples.length > SAMPLE_COUNT) {
      samples.shift();
    }
    
    if (samples.length === SAMPLE_COUNT) {
      const first = samples[0];
      const last = samples[samples.length - 1];
      const growth = last / first;
      
      if (growth > GROWTH_THRESHOLD) {
        console.warn('Potential memory leak detected!');
        console.warn('Memory grew by', ((growth - 1) * 100).toFixed(2), '%');
        
        // 触发详细分析或告警
        captureHeapSnapshot();
      }
    }
  }, 1000);
  
  return () => clearInterval(interval);
}

2.2 自动化测试

javascript
// Puppeteer内存测试
const puppeteer = require('puppeteer');

async function testMemoryLeak(url, actions) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  await page.goto(url);
  
  // 获取初始内存
  const initialMetrics = await page.metrics();
  const initialMemory = initialMetrics.JSHeapUsedSize;
  
  // 执行操作多次
  for (let i = 0; i < 50; i++) {
    await actions(page);
  }
  
  // 强制GC(需要启动Chrome时加--expose-gc)
  await page.evaluate(() => {
    if (window.gc) {
      window.gc();
    }
  });
  
  // 获取最终内存
  const finalMetrics = await page.metrics();
  const finalMemory = finalMetrics.JSHeapUsedSize;
  
  const growth = ((finalMemory - initialMemory) / initialMemory) * 100;
  
  console.log('Memory growth:', growth.toFixed(2), '%');
  
  if (growth > 50) {
    console.error('Potential memory leak detected!');
    
    // 拍摄heap snapshot
    const client = await page.target().createCDPSession();
    await client.send('HeapProfiler.enable');
    const { chunk } = await client.send('HeapProfiler.takeHeapSnapshot');
    // 保存snapshot用于分析
  }
  
  await browser.close();
  
  return growth;
}

// 使用
testMemoryLeak('http://localhost:3000', async (page) => {
  // 打开和关闭模态框
  await page.click('#open-modal');
  await page.waitForSelector('.modal');
  await page.click('#close-modal');
  await page.waitForFunction(() => !document.querySelector('.modal'));
});

2.3 React特定检测

javascript
// 检测React组件泄漏
function useComponentLeakDetection(componentName) {
  const mountCount = useRef(0);
  const unmountCount = useRef(0);
  
  useEffect(() => {
    mountCount.current++;
    
    console.log(`[${componentName}] Mounted. Total mounts: ${mountCount.current}`);
    
    return () => {
      unmountCount.current++;
      
      console.log(`[${componentName}] Unmounted. Total unmounts: ${unmountCount.current}`);
      
      // 检测泄漏
      if (mountCount.current - unmountCount.current > 10) {
        console.error(`[${componentName}] Potential leak: ${mountCount.current - unmountCount.current} instances not unmounted`);
      }
    };
  }, [componentName]);
}

// 使用
function MyComponent() {
  useComponentLeakDetection('MyComponent');
  
  return <div>Content</div>;
}

// 检测state和ref泄漏
function useMemoryLeakDetector() {
  const stateRefs = useRef(new Set());
  const refRefs = useRef(new Set());
  
  const trackState = useCallback((name) => {
    stateRefs.current.add(name);
  }, []);
  
  const trackRef = useCallback((name) => {
    refRefs.current.add(name);
  }, []);
  
  useEffect(() => {
    return () => {
      console.log('States:', stateRefs.current.size);
      console.log('Refs:', refRefs.current.size);
      
      if (stateRefs.current.size > 20) {
        console.warn('Too many states:', Array.from(stateRefs.current));
      }
    };
  }, []);
  
  return { trackState, trackRef };
}

第三部分:常见泄漏场景

3.1 定时器泄漏

javascript
// ❌ 泄漏示例
function BadTimer() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('tick');
    }, 1000);
    
    // 组件卸载,但timer仍在运行
  }, []);
  
  return <div>Component</div>;
}

// ✅ 正确清理
function GoodTimer() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('tick');
    }, 1000);
    
    return () => {
      clearInterval(timer);
    };
  }, []);
  
  return <div>Component</div>;
}

// 复杂场景:多个定时器
function MultipleTimers() {
  useEffect(() => {
    const timers = [];
    
    timers.push(setInterval(() => console.log('timer 1'), 1000));
    timers.push(setInterval(() => console.log('timer 2'), 2000));
    timers.push(setTimeout(() => console.log('timer 3'), 5000));
    
    return () => {
      timers.forEach(timer => {
        clearInterval(timer);
        clearTimeout(timer);
      });
    };
  }, []);
}

// requestAnimationFrame泄漏
function BadAnimation() {
  useEffect(() => {
    const animate = () => {
      console.log('animating');
      requestAnimationFrame(animate);
    };
    
    animate();
    
    // ❌ 无法停止
  }, []);
}

// 正确清理
function GoodAnimation() {
  useEffect(() => {
    let rafId;
    
    const animate = () => {
      console.log('animating');
      rafId = requestAnimationFrame(animate);
    };
    
    animate();
    
    return () => {
      if (rafId) {
        cancelAnimationFrame(rafId);
      }
    };
  }, []);
}

3.2 事件监听器泄漏

javascript
// ❌ 事件监听器泄漏
function BadEventListener() {
  useEffect(() => {
    const handleResize = () => {
      console.log('resized');
    };
    
    window.addEventListener('resize', handleResize);
    
    // ❌ 组件卸载,监听器仍存在
  }, []);
}

// ✅ 正确清理
function GoodEventListener() {
  useEffect(() => {
    const handleResize = () => {
      console.log('resized');
    };
    
    window.addEventListener('resize', handleResize);
    
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
}

// 多个监听器
function MultipleListeners() {
  useEffect(() => {
    const handlers = {
      resize: () => console.log('resize'),
      scroll: () => console.log('scroll'),
      click: () => console.log('click')
    };
    
    Object.entries(handlers).forEach(([event, handler]) => {
      window.addEventListener(event, handler);
    });
    
    return () => {
      Object.entries(handlers).forEach(([event, handler]) => {
        window.removeEventListener(event, handler);
      });
    };
  }, []);
}

// DOM元素监听器
function DOMListener() {
  const buttonRef = useRef();
  
  useEffect(() => {
    const button = buttonRef.current;
    
    const handleClick = () => console.log('clicked');
    
    if (button) {
      button.addEventListener('click', handleClick);
      
      return () => {
        button.removeEventListener('click', handleClick);
      };
    }
  }, []);
  
  return <button ref={buttonRef}>Click</button>;
}

3.3 订阅泄漏

javascript
// ❌ WebSocket泄漏
function BadWebSocket({ url }) {
  useEffect(() => {
    const ws = new WebSocket(url);
    
    ws.onmessage = (event) => {
      console.log(event.data);
    };
    
    // ❌ 组件卸载,WebSocket仍连接
  }, [url]);
}

// ✅ 正确清理
function GoodWebSocket({ url }) {
  useEffect(() => {
    const ws = new WebSocket(url);
    
    ws.onmessage = (event) => {
      console.log(event.data);
    };
    
    return () => {
      ws.close();
    };
  }, [url]);
}

// ❌ EventEmitter泄漏
function BadSubscription() {
  useEffect(() => {
    const subscription = eventEmitter.on('data', (data) => {
      console.log(data);
    });
    
    // ❌ 未取消订阅
  }, []);
}

// ✅ 正确清理
function GoodSubscription() {
  useEffect(() => {
    const subscription = eventEmitter.on('data', (data) => {
      console.log(data);
    });
    
    return () => {
      eventEmitter.off('data', subscription);
    };
  }, []);
}

// Observable订阅
function ObservableSubscription() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const subscription = observable.subscribe({
      next: (value) => setData(value),
      error: (err) => console.error(err)
    });
    
    return () => {
      subscription.unsubscribe();
    };
  }, []);
  
  return <div>{data}</div>;
}

3.4 闭包泄漏

javascript
// ❌ 闭包持有大对象
function BadClosure() {
  const [largeData] = useState(() => createLargeObject());
  
  useEffect(() => {
    const handler = () => {
      // 闭包引用largeData
      console.log(largeData.summary);
    };
    
    window.addEventListener('click', handler);
    
    return () => {
      window.removeEventListener('click', handler);
      // ❌ 但largeData仍被handler引用
    };
  }, []);  // 缺少largeData依赖
}

// ✅ 只保留需要的数据
function GoodClosure() {
  const [largeData] = useState(() => createLargeObject());
  const summary = useMemo(() => largeData.summary, [largeData]);
  
  useEffect(() => {
    const handler = () => {
      console.log(summary);  // 只引用小字符串
    };
    
    window.addEventListener('click', handler);
    
    return () => {
      window.removeEventListener('click', handler);
    };
  }, [summary]);
}

// ❌ 事件处理器闭包
function BadEventHandler() {
  const [items, setItems] = useState([]);
  
  const handleClick = () => {
    console.log(items);  // 闭包捕获items
  };
  
  useEffect(() => {
    window.addEventListener('click', handleClick);
    
    return () => {
      window.removeEventListener('click', handleClick);
    };
  }, []);  // ❌ 每次items变化,旧的items无法释放
}

// ✅ 使用useRef
function GoodEventHandler() {
  const [items, setItems] = useState([]);
  const itemsRef = useRef(items);
  
  useEffect(() => {
    itemsRef.current = items;
  });
  
  useEffect(() => {
    const handleClick = () => {
      console.log(itemsRef.current);  // 总是最新值
    };
    
    window.addEventListener('click', handleClick);
    
    return () => {
      window.removeEventListener('click', handleClick);
    };
  }, []);
}

注意事项

1. 第三方库

javascript
// 检查第三方库的清理
function ThirdPartyComponent() {
  useEffect(() => {
    const instance = ThirdPartyLib.init(element);
    
    return () => {
      // 查看文档,正确清理
      instance.destroy();
    };
  }, []);
}

2. 开发工具

javascript
// 开发环境检测泄漏
if (process.env.NODE_ENV === 'development') {
  // 使用why-did-you-render
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true
  });
}

3. 定期检查

javascript
// 定期内存审查
// - 每次重大功能后检查
// - 定期运行内存测试
// - 监控生产环境内存使用

常见问题

Q1: 如何快速定位泄漏源?

A: 使用Chrome Memory Profiler的对比快照功能。

Q2: 所有内存增长都是泄漏吗?

A: 不是,正常使用也会增长,关键看是否能被GC回收。

Q3: useEffect是泄漏的主要原因吗?

A: 是常见原因之一,尤其是缺少清理函数。

Q4: 如何测试泄漏修复有效?

A: 重复操作后检查内存是否稳定。

Q5: 生产环境如何检测泄漏?

A: 监控内存使用趋势,设置告警阈值。

Q6: 闭包一定会泄漏吗?

A: 不一定,但要注意闭包引用的对象大小。

Q7: React严格模式有帮助吗?

A: 有,双重调用可暴露cleanup问题。

Q8: 如何预防泄漏?

A: 代码审查、自动化测试、使用ESLint规则。

Q9: 内存泄漏影响性能多大?

A: 严重时可能导致卡顿和崩溃。

Q10: React 19对泄漏检测有改进吗?

A: 更严格的cleanup检查,更好的开发工具。

总结

核心要点

1. 泄漏原因
   ❌ 未清理定时器
   ❌ 未清理监听器
   ❌ 未清理订阅
   ❌ 闭包引用

2. 检测方法
   ✅ Chrome DevTools
   ✅ Performance Monitor
   ✅ Heap Snapshot
   ✅ 自动化测试

3. 常见模式
   ✅ 定时器泄漏
   ✅ 事件监听泄漏
   ✅ 订阅泄漏
   ✅ DOM引用泄漏

最佳实践

1. 预防措施
   ✅ 总是清理副作用
   ✅ 使用useRef避免闭包
   ✅ 限制缓存大小
   ✅ 定期审查代码

2. 检测流程
   ✅ 本地开发检测
   ✅ 自动化测试
   ✅ 生产监控
   ✅ 快速响应

3. 修复策略
   ✅ 定位泄漏源
   ✅ 添加清理逻辑
   ✅ 验证修复
   ✅ 回归测试

内存泄漏是隐性的性能杀手,建立完善的检测机制是保障应用长期稳定运行的关键。

第四部分:高级检测技术

4.1 内存快照对比

javascript
// 使用Chrome DevTools Memory Profiler进行快照对比
class HeapSnapshotComparator {
  constructor() {
    this.snapshots = [];
  }

  async takeSnapshot(label) {
    if (!performance.memory) {
      console.warn('Performance memory API not available');
      return null;
    }

    const snapshot = {
      label,
      timestamp: Date.now(),
      memory: {
        used: performance.memory.usedJSHeapSize,
        total: performance.memory.totalJSHeapSize,
        limit: performance.memory.jsHeapSizeLimit
      },
      components: this.captureComponentTree()
    };

    this.snapshots.push(snapshot);
    return snapshot;
  }

  captureComponentTree() {
    // 捕获当前组件树信息
    const tree = {};

    // 遍历React Fiber树(简化版)
    function traverse(fiber, path = []) {
      if (!fiber) return;

      const key = [...path, fiber.type?.name || 'Unknown'].join(' > ');
      tree[key] = (tree[key] || 0) + 1;

      if (fiber.child) traverse(fiber.child, [...path, fiber.type?.name]);
      if (fiber.sibling) traverse(fiber.sibling, path);
    }

    // 从root开始遍历
    if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.rendererInterfaces) {
      const renderers = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces;
      renderers.forEach(renderer => {
        if (renderer.getFiberRoots) {
          renderer.getFiberRoots(1).forEach(root => {
            traverse(root.current);
          });
        }
      });
    }

    return tree;
  }

  compareSnapshots(snapshot1Label, snapshot2Label) {
    const s1 = this.snapshots.find(s => s.label === snapshot1Label);
    const s2 = this.snapshots.find(s => s.label === snapshot2Label);

    if (!s1 || !s2) {
      throw new Error('Snapshots not found');
    }

    const memoryDiff = s2.memory.used - s1.memory.used;
    const percentChange = (memoryDiff / s1.memory.used) * 100;

    // 比较组件树
    const componentChanges = [];
    const allComponents = new Set([
      ...Object.keys(s1.components),
      ...Object.keys(s2.components)
    ]);

    allComponents.forEach(component => {
      const count1 = s1.components[component] || 0;
      const count2 = s2.components[component] || 0;

      if (count1 !== count2) {
        componentChanges.push({
          component,
          before: count1,
          after: count2,
          diff: count2 - count1
        });
      }
    });

    return {
      memoryDiff,
      percentChange: percentChange.toFixed(2),
      snapshot1: {
        label: s1.label,
        memory: s1.memory.used,
        timestamp: s1.timestamp
      },
      snapshot2: {
        label: s2.label,
        memory: s2.memory.used,
        timestamp: s2.timestamp
      },
      componentChanges: componentChanges.sort((a, b) => Math.abs(b.diff) - Math.abs(a.diff)),
      potentialLeaks: componentChanges.filter(c => c.diff > 0)
    };
  }

  generateReport() {
    if (this.snapshots.length < 2) {
      console.log('Need at least 2 snapshots to compare');
      return;
    }

    console.log('Heap Snapshot Analysis');
    console.log('=====================\n');

    for (let i = 1; i < this.snapshots.length; i++) {
      const comparison = this.compareSnapshots(
        this.snapshots[i - 1].label,
        this.snapshots[i].label
      );

      console.log(`Comparing: ${comparison.snapshot1.label} → ${comparison.snapshot2.label}`);
      console.log(`Memory Change: ${(comparison.memoryDiff / 1024 / 1024).toFixed(2)}MB (${comparison.percentChange}%)`);

      if (comparison.potentialLeaks.length > 0) {
        console.log('\nPotential Leaks:');
        comparison.potentialLeaks.slice(0, 5).forEach(leak => {
          console.log(`  ${leak.component}: +${leak.diff} instances`);
        });
      }

      console.log('');
    }
  }
}

// 使用
const comparator = new HeapSnapshotComparator();

// 测试内存泄漏场景
async function testMemoryLeak() {
  await comparator.takeSnapshot('Initial');

  // 执行可能泄漏的操作
  for (let i = 0; i < 10; i++) {
    // 打开/关闭模态框
    openModal();
    await new Promise(resolve => setTimeout(resolve, 100));
    closeModal();
  }

  await comparator.takeSnapshot('After 10 iterations');

  // 强制GC(需要在Chrome中启用--expose-gc)
  if (window.gc) {
    window.gc();
    await new Promise(resolve => setTimeout(resolve, 1000));
  }

  await comparator.takeSnapshot('After GC');

  comparator.generateReport();
}

4.2 自动泄漏检测

javascript
// 自动化内存泄漏检测系统
class AutoLeakDetector {
  constructor(options = {}) {
    this.options = {
      checkInterval: options.checkInterval || 60000,
      sampleSize: options.sampleSize || 10,
      growthThreshold: options.growthThreshold || 1.2,
      ...options
    };

    this.samples = [];
    this.detectionInterval = null;
    this.leaks = [];
  }

  start() {
    this.detectionInterval = setInterval(() => {
      this.checkMemory();
    }, this.options.checkInterval);

    console.log('Auto leak detector started');
  }

  stop() {
    if (this.detectionInterval) {
      clearInterval(this.detectionInterval);
      this.detectionInterval = null;
    }

    console.log('Auto leak detector stopped');
  }

  checkMemory() {
    if (!performance.memory) return;

    const sample = {
      timestamp: Date.now(),
      used: performance.memory.usedJSHeapSize,
      total: performance.memory.totalJSHeapSize
    };

    this.samples.push(sample);

    if (this.samples.length > this.options.sampleSize) {
      this.samples.shift();
    }

    if (this.samples.length === this.options.sampleSize) {
      this.analyzeGrowth();
    }
  }

  analyzeGrowth() {
    const first = this.samples[0];
    const last = this.samples[this.samples.length - 1];

    const growth = last.used / first.used;

    if (growth > this.options.growthThreshold) {
      const leak = {
        timestamp: Date.now(),
        growth: growth.toFixed(2),
        startMemory: (first.used / 1024 / 1024).toFixed(2) + ' MB',
        endMemory: (last.used / 1024 / 1024).toFixed(2) + ' MB',
        increase: ((last.used - first.used) / 1024 / 1024).toFixed(2) + ' MB',
        duration: last.timestamp - first.timestamp
      };

      this.leaks.push(leak);
      this.onLeakDetected(leak);
    }
  }

  onLeakDetected(leak) {
    console.error('Memory Leak Detected!');
    console.error(`Growth: ${leak.growth}x`);
    console.error(`Memory increased from ${leak.startMemory} to ${leak.endMemory}`);
    console.error(`Increase: ${leak.increase} over ${leak.duration}ms`);

    // 发送警告到监控系统
    this.reportLeak(leak);

    // 触发详细分析
    this.captureDetailedInfo();
  }

  reportLeak(leak) {
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/memory-leak', JSON.stringify(leak));
    }
  }

  captureDetailedInfo() {
    // 捕获当前状态详细信息
    const info = {
      url: window.location.href,
      userAgent: navigator.userAgent,
      componentTree: this.getComponentTree(),
      eventListeners: this.getEventListenerCount(),
      timers: this.getActiveTimers()
    };

    console.log('Detailed Leak Info:', info);
    return info;
  }

  getComponentTree() {
    // 获取React组件树信息
    const components = new Map();

    if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
      // 简化的组件统计
      // 实际实现需要遍历Fiber树
    }

    return Array.from(components.entries());
  }

  getEventListenerCount() {
    // 粗略估算事件监听器数量
    let count = 0;

    const events = ['click', 'scroll', 'resize', 'mousemove', 'keydown'];
    events.forEach(event => {
      const listeners = getEventListeners(document);
      if (listeners[event]) {
        count += listeners[event].length;
      }
    });

    return count;
  }

  getActiveTimers() {
    // 获取活动定时器数量(需要浏览器支持)
    return {
      intervals: window.setInterval.length || 'unknown',
      timeouts: window.setTimeout.length || 'unknown'
    };
  }

  getLeakReport() {
    return {
      totalLeaks: this.leaks.length,
      leaks: this.leaks,
      currentMemory: performance.memory ? {
        used: (performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
        total: (performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2) + ' MB'
      } : null
    };
  }
}

// 使用
const leakDetector = new AutoLeakDetector({
  checkInterval: 30000,  // 每30秒检查一次
  sampleSize: 10,
  growthThreshold: 1.3   // 30%增长触发警告
});

leakDetector.start();

// 定期报告
setInterval(() => {
  const report = leakDetector.getLeakReport();
  console.log('Leak Detection Report:', report);
}, 300000);  // 每5分钟

4.3 组件生命周期追踪

javascript
// 追踪组件生命周期以检测泄漏
class ComponentLifecycleTracker {
  constructor() {
    this.components = new Map();
  }

  register(componentId, componentName) {
    this.components.set(componentId, {
      name: componentName,
      mountTime: Date.now(),
      unmountTime: null,
      renderCount: 0,
      subscriptions: new Set(),
      timers: new Set(),
      listeners: new Set()
    });
  }

  unregister(componentId) {
    const component = this.components.get(componentId);

    if (component) {
      component.unmountTime = Date.now();

      // 检查是否清理了所有资源
      this.checkCleanup(componentId, component);
    }
  }

  trackRender(componentId) {
    const component = this.components.get(componentId);
    if (component) {
      component.renderCount++;
    }
  }

  trackSubscription(componentId, subscription) {
    const component = this.components.get(componentId);
    if (component) {
      component.subscriptions.add(subscription);
    }
  }

  trackTimer(componentId, timerId) {
    const component = this.components.get(componentId);
    if (component) {
      component.timers.add(timerId);
    }
  }

  trackListener(componentId, element, event, handler) {
    const component = this.components.get(componentId);
    if (component) {
      component.listeners.add({ element, event, handler });
    }
  }

  checkCleanup(componentId, component) {
    const issues = [];

    if (component.subscriptions.size > 0) {
      issues.push({
        type: 'subscriptions',
        count: component.subscriptions.size,
        message: `${component.subscriptions.size} subscriptions not unsubscribed`
      });
    }

    if (component.timers.size > 0) {
      issues.push({
        type: 'timers',
        count: component.timers.size,
        message: `${component.timers.size} timers not cleared`
      });
    }

    if (component.listeners.size > 0) {
      issues.push({
        type: 'listeners',
        count: component.listeners.size,
        message: `${component.listeners.size} event listeners not removed`
      });
    }

    if (issues.length > 0) {
      console.error(`[LEAK] ${component.name} has cleanup issues:`);
      issues.forEach(issue => {
        console.error(`  - ${issue.message}`);
      });

      return issues;
    }

    return null;
  }

  getLeakyComponents() {
    const leaky = [];

    this.components.forEach((component, id) => {
      if (component.unmountTime) {
        const issues = this.checkCleanup(id, component);
        if (issues) {
          leaky.push({
            id,
            name: component.name,
            issues,
            lifetime: component.unmountTime - component.mountTime,
            renderCount: component.renderCount
          });
        }
      }
    });

    return leaky;
  }

  generateReport() {
    const leakyComponents = this.getLeakyComponents();

    console.log('Component Lifecycle Analysis');
    console.log('===========================\n');

    if (leakyComponents.length === 0) {
      console.log('No memory leaks detected');
      return;
    }

    console.log(`Found ${leakyComponents.length} components with potential leaks:\n`);

    leakyComponents.forEach((component, index) => {
      console.log(`${index + 1}. ${component.name}`);
      console.log(`   Lifetime: ${component.lifetime}ms`);
      console.log(`   Renders: ${component.renderCount}`);
      console.log(`   Issues:`);
      component.issues.forEach(issue => {
        console.log(`     - ${issue.message}`);
      });
      console.log('');
    });
  }
}

// React Hook集成
const lifecycleTracker = new ComponentLifecycleTracker();

function useLifecycleTracking(componentName) {
  const componentId = useId();

  useEffect(() => {
    lifecycleTracker.register(componentId, componentName);

    return () => {
      lifecycleTracker.unregister(componentId);
    };
  }, [componentId, componentName]);

  useEffect(() => {
    lifecycleTracker.trackRender(componentId);
  });

  const trackSubscription = useCallback((subscription) => {
    lifecycleTracker.trackSubscription(componentId, subscription);
  }, [componentId]);

  const trackTimer = useCallback((timerId) => {
    lifecycleTracker.trackTimer(componentId, timerId);
  }, [componentId]);

  const trackListener = useCallback((element, event, handler) => {
    lifecycleTracker.trackListener(componentId, element, event, handler);
  }, [componentId]);

  return { trackSubscription, trackTimer, trackListener };
}

// 使用
function MyComponent() {
  const { trackSubscription, trackTimer, trackListener } = useLifecycleTracking('MyComponent');

  useEffect(() => {
    const subscription = observable.subscribe(data => {
      console.log(data);
    });

    trackSubscription(subscription);

    return () => {
      subscription.unsubscribe();
    };
  }, [trackSubscription]);

  return <div>Component</div>;
}

4.4 弱引用优化

javascript
// 使用WeakMap/WeakSet防止内存泄漏
class MemorySafeCache {
  constructor() {
    this.cache = new WeakMap();
    this.refs = new WeakSet();
  }

  set(key, value) {
    if (typeof key === 'object' && key !== null) {
      this.cache.set(key, value);
      this.refs.add(key);
      return true;
    }
    return false;
  }

  get(key) {
    return this.cache.get(key);
  }

  has(key) {
    return this.cache.has(key);
  }

  // 检查对象是否被GC
  isGarbageCollected(key) {
    return !this.refs.has(key);
  }
}

// DOM节点弱引用管理
class DOMNodeManager {
  constructor() {
    this.nodes = new WeakMap();
    this.observers = new WeakMap();
  }

  registerNode(node, metadata) {
    this.nodes.set(node, metadata);

    // 使用MutationObserver监听节点移除
    const observer = new MutationObserver((mutations) => {
      mutations.forEach(mutation => {
        mutation.removedNodes.forEach(removed => {
          if (removed === node) {
            this.onNodeRemoved(node);
          }
        });
      });
    });

    observer.observe(node.parentNode || document.body, {
      childList: true
    });

    this.observers.set(node, observer);
  }

  onNodeRemoved(node) {
    const observer = this.observers.get(node);
    if (observer) {
      observer.disconnect();
    }

    console.log('Node removed and cleaned up:', this.nodes.get(node));
  }

  getNodeInfo(node) {
    return this.nodes.get(node);
  }
}

// React组件弱引用
function useWeakRef(initialValue) {
  const weakRef = useRef(new WeakRef(initialValue));

  const getValue = useCallback(() => {
    return weakRef.current.deref();
  }, []);

  const setValue = useCallback((newValue) => {
    weakRef.current = new WeakRef(newValue);
  }, []);

  return [getValue, setValue];
}

// 使用
function ComponentWithWeakRef() {
  const [getHeavyData, setHeavyData] = useWeakRef({});

  useEffect(() => {
    const data = createHeavyObject();
    setHeavyData(data);

    // data可以被GC,即使组件还在
  }, [setHeavyData]);

  const handleClick = () => {
    const data = getHeavyData();
    if (data) {
      console.log('Data still available:', data);
    } else {
      console.log('Data was garbage collected');
    }
  };

  return <button onClick={handleClick}>Check Data</button>;
}

总结升级

高级检测技术总结

1. 内存快照对比
   - 多点快照
   - 组件树对比
   - 增长分析
   - 泄漏定位

2. 自动检测
   - 定期采样
   - 趋势分析
   - 自动告警
   - 详细报告

3. 生命周期追踪
   - 资源注册
   - 清理验证
   - 问题识别
   - 修复建议

4. 弱引用优化
   - WeakMap/WeakSet
   - DOM管理
   - 自动GC
   - 内存安全

完整检测流程

预防阶段:
☐ 代码规范
☐ 清理检查
☐ 弱引用使用
☐ 资源管理

检测阶段:
☐ 快照对比
☐ 自动监控
☐ 生命周期追踪
☐ 异常告警

分析阶段:
☐ 泄漏定位
☐ 原因分析
☐ 影响评估
☐ 修复方案

修复阶段:
☐ 代码修正
☐ 清理完善
☐ 回归测试
☐ 持续监控

持续改进:
☐ 定期审查
☐ 模式总结
☐ 工具优化
☐ 团队培训

内存泄漏防治需要系统化的方法,从预防到检测到修复形成闭环,确保应用长期稳定运行。