V8引擎的内存管理

2,119 阅读4分钟

V8引擎如何回收垃圾

V8的内存分配

和操作系统有关,64位操作系统,V8最多使用大约1.4GB的内存空间。

为什么要设计为1.4GB,而不设计更大的内存空间?

  1. JS最初设计是在浏览器上运行的,浏览器上的JS不持久,1.4G足够使用。
  2. JS回收垃圾会暂停所有代码执行,内存越大回收时占用的时间越多。

V8将自己的内存空间分为两部分:新生代内存空间和老生代内存空间。

新生代内存空间

新生代内存空间占用64MB,分为semi space Fromsemi space To两部分。

存放的是存活时间比较短的变量,会频繁发生垃圾回收。

本质是复制操作。

流程:

  1. 新建变量存放在From中,回收时标记From中活着的变量,将To空间清除,然后把活着的变量复制到To中。

  2. 后面再次回收时标记To中活着的变量,将From空间清除,然后把活着的变量复制到From中。

为什么使用这种设计?用空间换取时间,这种设计速度快,不需要进行内存整理,直接清空复制。

老生代内存空间

老生代内存空间占用1.4GB。本质是标记删除整理。

流程:

  1. 标记已死变量,删除已死变量。产生内存碎片(斑马纹)-> 标记清除(Mark Sweep)

  2. 整理内存空间 -> 标记合并(Mark Compact)

Mark Sweep

Mark Sweep是将需要被回收的对象进行标记,在垃圾回收运行时直接释放相应的地址空间,如下图所示(红色的内存区域表示需要被回收的区域):

如上图所示,使用 Mark Sweep 进行垃圾回收会产生一个问题,就是垃圾回收后内存会出现不连续的情况,为了解决这个问题,出现了 Mark Compact 方案。

Mark Compact

Mark Compact 的思想有点像新生代垃圾回收时采取的 Cheney 算法:将存活的对象移动到一边,将需要被回收的对象移动到另一边,然后对需要被回收的对象区域进行整体的垃圾回收。

新生代到老生代

满足两个条件:

  1. 变量在新生代内存空间经历过一次复制。
  2. 新生代内存空间中To部分或From部分已经占用25%。

变量处理

  • 内存主要就是存储变量等数据的;
  • 局部变量当程序执行结束,且没有引用的时候就会死掉;
  • 全局对象会始终存活到程序运行结束;

示例1:

function f () {
	var a = ''
}
f(); // f 执行结束 a 就会被回收

示例2:

function f () {
	var a = '';
	return a;
}
var b = f(); // f 执行结束 a 不会被回收,因为外层作用域还有 a 的引用 b。

查看V8内存的使用情况

通过node中的process.memoryUsage()查看

  1. 命令行输入node回车,再输入process.memoryUsage();,返回值可查看。

  • rss: V8申请到的总占用空间;
  • heapTotal: 堆总内存;
  • heapUsed: 已使用的内存;
  • external: node专有(底层是c,额外申请到的c++内存 );
  1. 代码查看:
function getMemory() {
    const memory = process.memoryUsage();
    const format = function(bytes) {
        return (bytes/1024/1024).toFixed(2) + 'MB';
    }
    console.log('Process: heapTotal ' + format(memory.heapTotal) + 
               ' heapUsed ' + format(memory.heapUsed) + 
               ' rss ' + format(memory.rss));
};

通过浏览器查看

  1. 在Chrome中F12调试工具Memory查看。
  2. Chrome中console中输入window.performance查看

内存优化

  1. 尽量不要定义全局变量。

  2. 全局变量使用完成后销毁。

示例:

a = undefined;	// 建议
delete a;	// 不建议使用,严格模式下会出问题
  1. 使用匿名自执行函数变全局为局部。

示例:

(function () {}())
var size = 20 * 1024 * 1024;
var arr1 = new Array(size);
var arr2 = new Array(size);
var arr3 = new Array(size);
var arr4 = new Array(size);
var arr5 = new Array(size);
var arr6 = new Array(size);
var arr7 = new Array(size);
var arr8 = new Array(size);
var arr9 = new Array(size);
getMemory(); // 内存溢出 极限 8 个

  1. 闭包?闭包并不会影响内存。

示例:

for (let i = 10000; i < 10100; i++) {
  setTimeout(function () {
    console.log(i);
    getMemory(); // 内存占用 4.x MB
  })
}

// 闭包形式
for (let i = 10000; i < 10100; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i);
      getMemory(); // 内存占用 4.x MB
    })
  })(i)
}

欢迎关注我的公众号,谢谢大家!