从面试题看Javascript的底层原理---V8引擎如何回收内存

2,630 阅读3分钟

面试题

var b = [];
for(var i=0;i<15;i++){
    b.push(new Array(20*1024*1024));
}

结果:

为什么我们要关注内存

  1. 防止页面占用内存过大,引起客户端卡顿,甚至无响应;
  2. Node使用的也是v8,内存对于后端服务的性能至关重要。因为服务的持久性,后端更容易造成内存溢出;

V8引擎内存回收机制

V8引擎的内存大小

  • 和操作系统有关64位为1.4G,32位为0.7G;
  • 64位下新生代的空间为64MB,老生代为1400MB;
  • 32为下新生代的空间为16MB,老生代为700MB;

注意:为什么浏览器没有把内存设置大点?
Javascript回收内存的时候会中断执行,如果内存过大,中断时间就会过长(一般100MB会需要3ms)

v8内存分配

  1. 最开始的变量放在新生代的from空间;
  2. 如果from的占有内存容量超过新生代的25%,则将from中的变量复制到to空间,然后清空from空间中的变量;
  3. 如果to的占有内存容量超过新生代的25%(注意:这里不是指占新生代总内存空间的25%,而是指to空间的25%),则将to中的变量复制到from空间,然后清空from空间中的变量;
  4. 如果一个变量复制超过一次,并且新生代中的内存超过25%,则放入老生代;
  5. 老生代通过标记、删除、整理方式进行垃圾回收(为什么不用标记、清除,因为该算法造成内存空间不连续问题,但是数组必须是内存连续的);

内存如何回收

内存查看

  • 浏览器 -- window.performance

  • Node -- process.memoryUsage();

function getMemory(){
    var memory = process.memoryUsage();
    function format(bytes){
        return (bytes/1024/1024).toFixed(2)+'MB'
    }
    console.log('headTotal: '+format(memory.heapTotal)+'       headpUsed:  '+format(memory.heapUsed)
    );
}

var a = [];
var size = 20*1024*1024;
function b(){
    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);
}
b();
getMemory();
setInterval(() => {
   a.push(new Array(size));
   getMemory();
}, 1000);

  • 上图可以看出Node的内存情况
  1. 最开始打印出的内存使用是800MB左右;
  2. 然后降了,是因为内存使用量已经到达了垃圾回收的标准,回收了局部变量arr1,arr2,arr3,arr4,arr5;
  3. 后面内存一直往上增,是因为全局变量a的内存一直新增,但是它是一个全局变量,无法被垃圾回收,所以导致堆内存溢出;

优化内存的技巧

  • 尽量不要定义全局变量
  • 全局变量记得销毁掉
  • 用匿名自执行函数变全局为局部
  • 尽量避免闭包引用

容易引发内存使用不当的情景

  • 滥用全局变量
    如果一定需要使用全局变量,尽量手动回收: a = undefined;
  • 缓存不限制
    Node缓存是个全局变量,如果一直往缓存加东西,内存会溢出,建议将缓存放redis、给缓存加锁等方式来解决;
//例如通过判断长度给缓存加锁,这样b中就一直只会有5个元素
var b = [];
for(var i=0;i<15;i++){
    if(b.length>4){
        b.shift();
    }
    b.push(new Array(20*1024*1024));
}
  • 操作大文件
  1. 浏览器:用input上传大文件,浏览器直接卡死;
    解决办法:切片上传、断点续传。file.slice(0,1000);
  2. node:大文件读取、不能用fs.readFile(),因为这个方法是一次性读取文件到buffer;
    解决办法:用流的方式读取,createReadStream().pipe(write)