Appearance
内存泄漏识别
第一部分:内存泄漏基础
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
- 内存安全完整检测流程
预防阶段:
☐ 代码规范
☐ 清理检查
☐ 弱引用使用
☐ 资源管理
检测阶段:
☐ 快照对比
☐ 自动监控
☐ 生命周期追踪
☐ 异常告警
分析阶段:
☐ 泄漏定位
☐ 原因分析
☐ 影响评估
☐ 修复方案
修复阶段:
☐ 代码修正
☐ 清理完善
☐ 回归测试
☐ 持续监控
持续改进:
☐ 定期审查
☐ 模式总结
☐ 工具优化
☐ 团队培训内存泄漏防治需要系统化的方法,从预防到检测到修复形成闭环,确保应用长期稳定运行。