内存泄漏排查真经

56 阅读3分钟

内存泄漏排查真经

文/ 玄冥派内存监察使

(虚空之中,黑袍长老手持罗盘法器,罗盘上指针疯狂旋转)

"今日传授尔等内存监察大法。内存泄漏如同修士心魔,初时不显,日久必成大道之阻。且看这五方镇魔大阵——"


第一章:泄漏五方境界

1. 常规泄漏(土象)

// 未清理的全局变量
function leakEarth() {
  leakedData = new Array(1000000).fill('*'); // 百万级数据泄漏
}

2. 闭包泄漏(火象)

// 闭包持有大对象
function leakFire() {
  const hugeData = getHugeData();
  return function() {
    console.log(hugeData.length); // 闭包持续引用
  };
}

3. DOM泄漏(木象)

// 未解绑的DOM引用
function leakWood() {
  const button = document.getElementById('myButton');
  button.addEventListener('click', () => {
    console.log('Button clicked');
  });
  // 移除元素但未移除事件监听
  button.remove(); 
}

4. 定时器泄漏(金象)

// 未清除的定时器
function leakMetal() {
  setInterval(() => {
    const data = new Array(10000);
    // 持续产生内存占用
  }, 1000);
}

5. 缓存泄漏(水象)

// 无限增长的缓存
const cache = new Map();
function leakWater(key, value) {
  if (cache.size > 1000) return; // 缺少清理逻辑
  cache.set(key, value);
}

第二章:排查七式

第一式:开天眼(控制台监控)

// 内存快照对比
console.profile('Memory Snapshot 1');
takeSnapshot();
console.profileEnd();

// 执行可疑操作后
console.profile('Memory Snapshot 2');
takeSnapshot();
console.profileEnd();

第二式:祭法器(DevTools)

# Chrome内存记录
chrome://memory-redirect/

第三式:观星象(性能监控)

// 实时内存监控
setInterval(() => {
  const memory = performance.memory;
  console.log(`Used: ${memory.usedJSHeapSize}KB`);
}, 1000);

第四式:画符咒(内存快照)

// 生成堆快照
function takeHeapSnapshot() {
  if (window.chrome && window.chrome.devtools) {
    window.chrome.devtools.inspectedWindow.takeHeapSnapshot();
  }
}

第五式:测灵脉(压力测试)

// 自动触发GC观察内存
function triggerGC() {
  if (window.gc) {
    window.gc();
  } else {
    console.warn('请使用--expose-gc参数启动Chrome');
  }
}

第六式:追魂术(引用追踪)

// 跟踪特定对象
function trackObject(obj) {
  const ref = new WeakRef(obj);
  setInterval(() => {
    if (!ref.deref()) {
      console.log('对象已被回收');
      clearInterval(this);
    }
  }, 1000);
}

第七式:断因果(隔离测试)

// 创建隔离环境
function testInSandbox(code) {
  const iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  iframe.contentWindow.eval(code);
  setTimeout(() => {
    iframe.remove();
  }, 1000);
}

第三章:防治心法

"内存监察五要:
1️⃣ 定期自查(开发阶段监控)
2️⃣ 边界测试(大数据量压测)
3️⃣ 及时清理(释放无用引用)
4️⃣ 工具辅助(善用分析工具)
5️⃣ 代码规范(避免已知陷阱)"

第四章:实战演练

闭包泄漏解决方案

// 修复闭包泄漏
function fixClosureLeak() {
  const hugeData = getHugeData();
  
  // 使用弱引用
  const weakRef = new WeakRef(hugeData);
  
  return function() {
    const data = weakRef.deref();
    if (data) {
      console.log(data.length);
    }
  };
}

DOM泄漏完整解决方案

class SafeDOM {
  constructor(element) {
    this.element = element;
    this.handlers = new Map();
  }

  addEventListener(type, handler) {
    const wrappedHandler = (...args) => handler(...args);
    this.handlers.set(handler, wrappedHandler);
    this.element.addEventListener(type, wrappedHandler);
  }

  remove() {
    for (const [handler, wrapped] of this.handlers) {
      this.element.removeEventListener(type, wrapped);
    }
    this.element.remove();
    this.element = null;
  }
}

第五章:禁忌案例

魔道写法

// 错误1:无限增长的数组
const logs = [];
function logMessage(message) {
  logs.push(`${Date.now()}: ${message}`);
}

// 错误2:未清理的观察者
class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(obs) {
    this.observers.push(obs);
  }
  // 缺少removeObserver方法
}

正道解法

// 正确1:限制缓存大小
class LRUCache {
  constructor(maxSize = 100) {
    this.maxSize = maxSize;
    this.cache = new Map();
  }

  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      const oldest = this.cache.keys().next().value;
      this.cache.delete(oldest);
    }
    this.cache.set(key, value);
  }
}

// 正确2:自动清理的观察者
class SafeSubject {
  constructor() {
    this.observers = new Set();
  }
  addObserver(obs) {
    this.observers.add(obs);
    return () => this.observers.delete(obs); // 返回清理函数
  }
}

(突然,罗盘指针剧烈抖动,出现内存溢出警告)

弟子:"师尊!Node进程内存突破2GB了!"

长老:"莫慌!此乃未释放的Stream导致,看老夫手段——"

长老掐诀念咒,虚空中浮现修复代码:

function fixStreamLeak() {
  const stream = createReadStream();
  
  // 自动销毁处理
  stream
    .on('data', processData)
    .on('end', () => stream.destroy())
    .on('error', () => stream.destroy());

  // 或者使用pipeline自动清理
  pipeline(
    stream,
    transformStream,
    processStream,
    (err) => {
      if (err) console.error(err);
    }
  );
}

飞升天象:

当内存监察修炼至大乘期,可:

  • 预判潜在泄漏点
  • 设计自清理架构
  • 实现零泄漏系统
  • 处理TB级内存管理

(长老化作五色流光,融入罗盘之中,浮现最后箴言)

"记住,内存之道在于'有借有还'。如同阴阳循环,分配必要及时释放......"

(罗盘展开,化作《内存监察真经》)

<真经展开,显现完整内存知识图谱> <第二元神显化:关注玄冥派,解锁更多内存秘法>


核心难点解析

1. 泄漏类型识别

graph TD
    A[内存持续增长] --> B[GC后不下降]
    B --> C{分析堆快照}
    C -->|全局变量| D[土象解法]
    C -->|闭包引用| E[火象解法]
    C -->|DOM游离| F[木象解法]

2. Node.js特殊泄漏

// 常见Node泄漏场景
const nodeLeaks = {
  '缓存未清理': {
    example: 'global.cache = {}',
    fix: '使用WeakMap或LRU缓存'
  },
  'Promise未处理': {
    example: 'new Promise(() => {...})',
    fix: '总是添加catch处理'
  },
  '事件监听未移除': {
    example: 'emitter.on(event, cb)',
    fix: '使用once或显式移除'
  }
};

3. 高级分析技巧

// 内存增长对比分析
function analyzeGrowth() {
  const snapshot1 = takeHeapSnapshot();
  // 执行操作...
  const snapshot2 = takeHeapSnapshot();
  
  return {
    sizeDiff: snapshot2.totalSize - snapshot1.totalSize,
    leakedTypes: compareSnapshots(snapshot1, snapshot2)
  };
}

4. 自动化检测方案

// 内存监控中间件
function memoryMonitor(req, res, next) {
  const startMem = process.memoryUsage();
  
  res.on('finish', () => {
    const endMem = process.memoryUsage();
    if (endMem.heapUsed - startMem.heapUsed > 1000000) {
      alertPotentialLeak(req.path);
    }
  });
  
  next();
}

5. 防御式编程规范

// 安全资源管理类
class SafeResource {
  constructor(resource) {
    this.resource = resource;
    this.closed = false;
  }

  use(callback) {
    if (this.closed) throw new Error('Resource closed');
    return callback(this.resource);
  }

  close() {
    cleanup(this.resource);
    this.resource = null;
    this.closed = true;
  }
}

// 使用示例
const resource = new SafeResource(createResource());
try {
  resource.use(res => {
    // 安全使用资源
  });
} finally {
  resource.close();
}