完整的 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 | 连续大块内存 |
这种分代设计使得垃圾回收器能够针对不同生命周期的对象使用最优的回收策略,大大提高了垃圾回收的效率。