JS内存及其回收机制

798 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

这是笔者一次参加公开课记下的笔记,希望能让前端的掘友对Javascript内存有个概念性的认识。

内存是用来存什么的

通俗的来说呢,就是用来存 varletfunctionconst声明的变量。

内存的大小

与操作系统有关。

64位32位
1.4GB0.7GB

为啥内存大小要这么设计,为啥不是越大越好

  1. 表象原因,1.4GB够用了 JavaScript设计之初是作为脚本语言(一次性的执行,执行完毕就直接释放),相对于javaC这些用来编写持久性的服务语言(内存一般不受限制)来说,够用了,想想你一次性的定义变量超过1.4GB还是有难度的(当然不要耍赖用循环)。

  2. 深层次的原因,JS每回收一次垃圾,会把整个代码的执行暂停, 回收200MB大概需要30ms,如果内存设计的太大,回收垃圾的时候会暂停很久,用户体验不好。

V8 的内存分配

新生代会频繁发生变量的移动,老生代存的比较久。

64位32位
新生代64MB 老生代1400MB新生代16MB 老生代700MB

image.png

新生代内存

存放生存的并不久的变量

老生代内存

存放常驻变量

变量从新生代 --> 老生代

1.新生代内存空间使用超过了25%(前置条件)。

2.经过了一次垃圾回收,但是还没有回收掉的变量(还有地方会用到的变量),使其变为常驻变量。

新生代回收算法

  1. 复制
  2. 删除

为什么分为两部分,因为新生代会频繁发生变量的移动,一开始变量都放在from, 比如abc三个变量,第一次回收以后ac还活着,那就只需要把ac放到to中,然后把from中的全部删除,同样的道理,下一次回收从to中把活着的变量复制到from, 删除to中的。只需要做两步 复制 删除,这样比较高效。

老生代回收算法

  1. 标记
  2. 清除
  3. 整理

先给需要回收的变量加标记,然后执行删除,删除之后呢,删除变量的位置会产生磁盘碎片,举个例子:数组(只能储存相同大小的同类型变量,在内存上必须是连续的空间) [1, ,3, ] 还有两个位置,但是如果我们现在要把[2,4]存进去,虽然还有两个空位置,但是不连续,是存不进去的,所以还需要进行一步整理磁盘碎片让不连续的内存碎片变得连续。

内存如何回收

内存快接近满时

  • 全局变量,没有执行完毕不会回收
  • 局部变量失去引用回收。

下面是一个例子,全局的arr,我们不停往里面push大数组,因为arr是全局的,不能被回收内存就爆了。

function printMe(){
    var mem = process.memoryUsage();
    var format = function(bytes){
        return (bytes/1024/1024).toFixed(0) + ' MB';
    };
    console.log('ToTal: ' + format(mem.heapTotal) + ' Used: ' + format(mem.heapUsed));
}

const arr = [];
const size = 30 * 1024 * 1024;
for (let i = 0; i < 15; i++) {
    arr.push(new Array(size));
    printMe();
}

image.png

在通过一个小例子来看下JS回收临时变量的过程:

function printMe(){
    var mem = process.memoryUsage();
    var format = function(bytes){
        return (bytes/1024/1024).toFixed(0) + 'MB';
    };
    console.log('ToTal: ' + format(mem.heapTotal) + 'Used: ' + format(mem.heapUsed));
}

const arr = [];
const size = 30 * 1024 * 1024;
function notGolbal() {
    var noarr = []; // 临时
    for (let i = 0; i < 3; i++) {
        noarr.push(new Array(size));
    }
}
notGolbal();
setInterval(()=>{
    arr.push(new Array(size));
    printMe();
}, 1000);

看图中圈出部分,已经进行了临时变量的回收。

image.png

开发应该注意些什么

1.能不定义为全局变量,就不要定义为全局变量,非得定义为全局变量,用完记得手动回收(设置值为null/undefined)。

2.如果用内存实现缓存要做限制,如果超过这个限制(先进先出),就把最开始的缓存清空,防止内存爆满。

3.上传大文件时,避免直接操作整个文件,(上传文件其实就是把文件先从硬盘读取到内存中,再从内存进行上传到服务器的操作,如果文件过大,内存可能扛不住),把文件切片上传。

性能监控方案

  • Lighthouse -谷歌推出的,可直接在浏览器安装(需要翻墙),也可以通过npm安装,通过命令行来对网站的性能做测试。比如以下命令的意思就是检测 juejin.cn/ 的性能,将结果输出为HTML,路径为当前命令行执行的路径。
lighthouse https://juejin.cn/ --output=html -path ./
  • window.performance的数据进行分析,然后自行优化。

总结

  1. 简单说,JavaScript内存就是用来存变量,函数的
  2. 内存的大小在不同的操作系统上也不同,64位的操作系统是1.4G,32位是0.7G
  3. 内存分配:分为新生代和老生代
  4. 新生代用来存放 生存的不久的变量
  5. 老生代用来存放常驻变量
  6. 新生代回收内存主要两个步骤:复制,删除(新生代分为fromto两部分,第一次回收过后,把from中存在的变量复制到to,清空from,第二次回收过后,把to中存在的变量复制到from,清空to。依此类推)
  7. 老生代回收主要三个步骤:标记,清除,整理(先给需要回收的变量加标记,然后清除,清除后会导致内存变得不连续,因此需要进行整理)

感谢掘友的观看🌹🌹🌹

如果觉得对您有点帮助,点个赞支持下🌹🌹🌹

如果笔者写的不对的,还望不吝赐教噢🌹🌹🌹