终于记住Javascript垃圾回收机制

完整的 GC Roots 分类

栈中的 Roots(栈根)

通过移动 ESP 指针(记录当前执行状态的指针)实现垃圾回收。执行栈中当一个函数执行完毕,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。

rust 复制代码
//调用栈
Step1
  ESP--> funcB执行上下文
         funcA执行上下文
         全局执行上下文
Step2      
 销毁-->  funcB执行上下文
 ESP-->  funcA执行上下文
         全局执行上下文        

堆中的 Roots(堆根)

javascript 复制代码
// 堆中的GC Roots
const globalObj = { name: "全局对象" }; // ← 在堆中,但作为GC Root

// 其他堆中的Roots:
const heapRoots = {
  globalObjects: globalThis,           // 全局对象
  eventListeners: new Map(),           // 事件监听器表
  timerCallbacks: new Set(),           // 定时器回调
  moduleCache: require?.cache,         // 模块缓存
  staticFields: Class.staticProperty,  // 静态字段
};

比较显性看出上述内容GC不妥当可能会导致栈溢出 我来详细解释垃圾回收机制中新生代和老生代的区分原理。

内存分代理论

大多数垃圾回收器都基于 "弱分代假说"

  • 大多数对象的生命周期都很短
  • 存活时间长的对象倾向于继续存活更久

基于这个理论,内存被分为两个主要区域:

新生代 (Young Generation)

特征

  • 存放新创建的对象
  • 空间较小(通常 1-8MB)
  • 垃圾回收频繁
  • 回收速度快

内部结构

css 复制代码
新生代内存布局:
┌─────────────────┐
│    Eden 区      │ ← 新对象分配在这里
├─────────────────┤
│   Survivor 区   │ ← From 空间 (S0)
│                 │ ← To 空间   (S1)
└─────────────────┘

回收过程 - Scavenge 算法

javascript 复制代码
// 模拟新生代GC过程
class YoungGeneration {
  constructor() {
    this.eden = [];        // Eden 区
    this.survivorFrom = []; // Survivor From 区
    this.survivorTo = [];   // Survivor To 区
  }
  
  // 对象分配
  allocate(object) {
    this.eden.push(object);
    
    // 当 Eden 区满时触发 Minor GC
    if (this.eden.length >= EDEN_SIZE) {
      this.minorGC();
    }
  }
  
  // 新生代垃圾回收
  minorGC() {
    // 1. 标记存活对象
    const liveObjects = this.markLiveObjects();
    
    // 2. 将存活对象复制到 Survivor To 区
    this.survivorTo = [...liveObjects];
    
    // 3. 清空 Eden 和 Survivor From 区
    this.eden = [];
    
    // 4. 交换 Survivor 区
    [this.survivorFrom, this.survivorTo] = [this.survivorTo, this.survivorFrom];
  }
}

对象晋升机制

javascript 复制代码
// 对象在新生代中的生命周期
function objectLifecycle() {
  let age = 0; // 对象年龄计数器
  
  // 对象每次在新生代GC中存活,年龄+1
  function surviveGC() {
    age++;
    
    // 当年龄达到阈值(通常是2),晋升到老生代
    if (age >= AGE_THRESHOLD) {
      promoteToOldGeneration(this);
    }
  }
}

老生代 (Old Generation)

特征

  • 存放长期存活的对象
  • 空间较大(通常几百MB到几GB)
  • 垃圾回收不频繁但耗时较长
  • 使用不同的回收算法

回收算法

1. 标记-清除 (Mark-Sweep)

javascript 复制代码
class MarkSweepGC {
  markSweep() {
    // 第一阶段:标记
    function mark(root) {
      if (root.isReachable && !root.marked) {
        root.marked = true;
        root.children.forEach(child => mark(child));
      }
    }
    
    // 第二阶段:清除
    function sweep() {
      memory.forEach(object => {
        if (!object.marked) {
          freeMemory(object); // 回收内存
        } else {
          object.marked = false; // 重置标记位
        }
      });
    }
  }
}

2. 标记-整理 (Mark-Compact)

javascript 复制代码
class MarkCompactGC {
  markCompact() {
    // 标记阶段(同标记-清除)
    this.mark();
    
    // 整理阶段:移动存活对象,消除内存碎片
    let newPosition = 0;
    
    memory.forEach(object => {
      if (object.marked) {
        // 移动对象到连续内存区域
        moveObject(object, newPosition);
        newPosition += object.size;
      }
    });
    
    // 更新所有引用的指针
    this.updateReferences();
  }
}

分代回收的完整流程

javascript 复制代码
// 完整的垃圾回收流程
class GenerationalGC {
  constructor() {
    this.youngGen = new YoungGeneration();
    this.oldGen = new OldGeneration();
  }
  
  // 对象分配
  allocate(object) {
    // 新对象总是分配到新生代
    return this.youngGen.allocate(object);
  }
  
  // 垃圾回收触发
  collectGarbage() {
    // 1. 先执行新生代GC(Minor GC)
    const promotedObjects = this.youngGen.minorGC();
    
    // 2. 晋升存活对象到老生代
    promotedObjects.forEach(obj => {
      this.oldGen.allocate(obj);
    });
    
    // 3. 检查老生代内存使用率
    if (this.oldGen.memoryUsage > THRESHOLD) {
      // 触发老生代GC(Major GC/Full GC)
      this.oldGen.majorGC();
    }
  }
}

实际示例

V8 引擎的分代内存管理

javascript 复制代码
// 查看 V8 内存使用情况
const v8 = require('v8');

function printMemoryStatistics() {
  const heapStatistics = v8.getHeapStatistics();
  
  console.log('新生代内存限制:', heapStatistics.young_gen_size_limit);
  console.log('老生代内存限制:', heapStatistics.old_gen_size_limit);
  console.log('总堆大小:', heapStatistics.heap_size_limit);
  console.log('已使用堆大小:', heapStatistics.used_heap_size);
}

// 模拟不同生命周期的对象
function simulateObjectLifetimes() {
  // 短生命周期对象(在新生代中回收)
  for (let i = 0; i < 1000; i++) {
    const tempObj = { data: `temporary-${i}` };
    // 这些对象很快会在新生代GC中被回收
  }
  
  // 长生命周期对象(晋升到老生代)
  const longLivedObj = { 
    data: "长期存活的数据",
    cache: new Array(1000).fill('cache data')
  };
  
  // 全局变量,持续被引用
  global.longLivedReference = longLivedObj;
}

性能优化建议

1. 减少全局变量

javascript 复制代码
// 不好:全局变量会一直存活在老生代
global.cache = new Map();

// 更好:使用模块作用域
const cache = new Map();
export function getData() { /* ... */ }

2. 及时释放引用

javascript 复制代码
function processLargeData() {
  const largeData = loadHugeDataset();
  
  // 处理数据...
  processData(largeData);
  
  // 及时释放引用
  largeData = null; // 帮助GC识别可回收对象
}

3. 避免内存泄漏

javascript 复制代码
// 闭包中的内存泄漏
function createLeak() {
  const largeArray = new Array(1000000);
  
  return function() {
    // 闭包持有 largeArray 引用,即使外部不再需要
    console.log('leak');
  };
}

总结

特性 新生代 老生代
对象类型 新创建对象 长期存活对象
空间大小 小 (1-8MB) 大 (几百MB-GB)
回收频率
回收速度
回收算法 Scavenge 标记-清除/标记-整理
内存布局 Eden + Survivor 连续大块内存

这种分代设计使得垃圾回收器能够针对不同生命周期的对象使用最优的回收策略,大大提高了垃圾回收的效率。

相关推荐
举个栗子dhy5 小时前
第二章、全局配置项目主题色(主题切换+跟随系统)
前端·javascript·react.js
sorryhc6 小时前
开源的SSR框架都是怎么实现的?
前端·javascript·架构
fox_6 小时前
别再混淆 call/apply/bind 了!一篇讲透用法、场景与手写逻辑(二)
前端·javascript
潜心编码6 小时前
基于vue的停车场管理系统
前端·javascript·vue.js
T___T6 小时前
从 "送花被拒" 到 "修成正果":用 JS 揭秘恋爱全流程中的对象与代理魔法
前端·javascript
三小河6 小时前
从私服版本冲突到依赖治理:揭秘 resolutions 配置
前端·javascript·架构
举个栗子dhy6 小时前
第一章、React + TypeScript + Webpack项目构建
前端·javascript·react.js
大杯咖啡6 小时前
localStorage与sessionStorage的区别
前端·javascript
非凡ghost7 小时前
HWiNFO(专业系统信息检测工具)
前端·javascript·后端