阿里云主机折上折
  • 微信号
Current Site:Index > Common patterns and pitfalls of memory leaks

Common patterns and pitfalls of memory leaks

Author:Chuan Chen 阅读数:40966人阅读 分类: JavaScript

The Nature and Hazards of Memory Leaks

Memory leaks occur when a program fails to release memory that is no longer in use, leading to a gradual reduction in available memory. Although JavaScript's garbage collection (GC) mechanism automatically manages memory, developers' misjudgment of reference relationships can still result in memory that cannot be reclaimed. Persistent memory leaks can cause page lag or crashes, particularly in long-running Single-Page Applications (SPAs), where the harm is especially pronounced.

Accidental Global Variables

Undeclared variables are attached to the global object (window in browsers) and will continue to occupy memory unless actively cleared:

function createGlobalLeak() {
  leakedArray = new Array(1000000); // No var/let/const used
}

Strict mode can prevent such issues:

'use strict';
function preventLeak() {
  let localArray = []; // Correctly scoped
}

Forgotten Timers and Callbacks

Uncleared setInterval and event listeners are common sources of leaks:

const timer = setInterval(() => {
  const node = document.getElementById('node');
  if(node) node.textContent = new Date();
}, 1000);

// If the node is removed but the timer isn't cleared, related references remain

The correct approach is to pair them with cleanup:

const node = document.getElementById('node');
const clickHandler = () => console.log('Clicked');

node.addEventListener('click', clickHandler);

// When removing the node, also remove the listener
node.removeEventListener('click', clickHandler);
node.parentNode.removeChild(node);

DOM References and Detached Nodes

DOM references retained in JavaScript can prevent the entire DOM tree from being reclaimed:

const cache = {};
function saveNodeReference() {
  const node = document.getElementById('largeElement');
  cache.storedNode = node; // Even if removed from the DOM, it remains accessible via cache
}

document.body.removeChild(document.getElementById('largeElement'));

Using WeakMap avoids strong references:

const weakCache = new WeakMap();
const node = document.getElementById('largeElement');
weakCache.set(node, { metadata: 'temp' }); // Automatically released when the node is removed

Implicit References Due to Closures

Function closures may inadvertently capture large objects:

function createClosureLeak() {
  const hugeData = new Array(1000000).fill('*');
  return function() {
    console.log('Closure holds:', hugeData.length); // hugeData is retained by the closure
  };
}

The optimized solution is to release local variables promptly:

function fixClosureLeak() {
  const hugeData = new Array(1000000).fill('*');
  // Actively dereference after use
  const result = processData(hugeData);
  return function() {
    console.log('Result:', result); // Only necessary data is retained
  };
}

Improper Caching Strategies

Unbounded cache growth continuously consumes memory:

const dataCache = [];
function cacheData(data) {
  dataCache.push(data); // No cleanup mechanism
}

Implement cache eviction policies like LRU:

class LRUCache {
  constructor(maxSize) {
    this.maxSize = maxSize;
    this.cache = new Map();
  }
  
  set(key, value) {
    if(this.cache.size >= this.maxSize) {
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }
    this.cache.set(key, value);
  }
}

Framework-Specific Leak Patterns

In React, uncleaned side effects are a common issue:

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);
  
  // Missing cleanup function leaves listeners active after component unmounting
  return () => window.removeEventListener('resize', handleResize);
}, []);

Timer leaks in Vue:

export default {
  mounted() {
    this.timer = setInterval(this.updateData, 1000);
  },
  beforeDestroy() { // Vue2
    clearInterval(this.timer);
  },
  onBeforeUnmount() { // Vue3
    clearInterval(this.timer);
  }
}

Detection and Diagnostic Tools

Chrome DevTools' Memory panel provides key capabilities:

  • Heap Snapshot comparison to locate unreleased objects
  • Allocation Timeline to record memory allocation sequences
  • Performance Monitor to track real-time memory changes

Example diagnostic process:

  1. Take an initial heap snapshot
  2. Perform suspicious operations
  3. Take a second snapshot
  4. Compare and filter newly allocated but unreleased objects

Defensive Programming Practices

Use object pools to reuse large objects:

class ObjectPool {
  constructor(createFn) {
    this.createFn = createFn;
    this.pool = [];
  }
  
  acquire() {
    return this.pool.pop() || this.createFn();
  }
  
  release(obj) {
    this.pool.push(obj);
  }
}

// Usage example
const pool = new ObjectPool(() => new Array(1000));
const array = pool.acquire();
// After use
pool.release(array);

For event listeners, adopt automatic binding/unbinding patterns:

class EventManager {
  constructor() {
    this.handlers = new WeakMap();
  }

  bind(target, event, handler) {
    target.addEventListener(event, handler);
    this.handlers.set(target, { event, handler });
  }

  unbindAll() {
    this.handlers.forEach((value, target) => {
      target.removeEventListener(value.event, value.handler);
    });
  }
}

Memory Management Design Principles

  1. Reference Transparency: Clearly know which code holds object references
  2. Lifecycle Symmetry: Pair allocation and deallocation operations
  3. Capacity Boundaries: All cache structures must have size limits
  4. Environment Adaptation: Adjust resource usage strategies based on device memory

Example memory-sensitive component:

class MemoryAwareComponent {
  constructor() {
    this.memoryBudget = navigator.deviceMemory * 1024 * 1024 / 2;
    this.cache = new LRUCache(this.memoryBudget / 1e6);
  }

  loadResource(url) {
    if(performance.memory.usedJSHeapSize > this.memoryBudget) {
      this.cache.clearExpired();
    }
    // ...loading logic
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.