内存管理

121 阅读4分钟

内存管理

减少浏览器负担 -- 内存过大会导致浏览器压力过大 -- 浏览器卡顿

内存不够 -- 服务中断

内存数据存储

内存分类

  1. 栈内存,先进后出,线性连续的数据结构,
  2. 堆内存,树状结构,非线性不连续的数据结构

变量存储

  • 普通类型变量,直接存在栈里,值和变量本身存在一起,变量直接指向值
  • 方法、对象、数组等引用类型,在堆内存开创地址储存对象本身,将它赋值给一个变量后,就在栈内存存地址(在栈内存加入这个变量,让这个变量指向堆内存中对应的地址,而不是变量本身)

V8内存管理

V8限制了所有能用的内存极限,64位下是1.4G,32位下700M,根据浏览器不同有些许扩容,Node情况下会有C++内存扩容

v8内存构成:新生代内存区,老生代内存区,大对象区,代码区,Map区

  • 新生代,短时间存活的新变量存在新生代中,新生代内存极小,64位下大概32MB
  • 老生代,生存时间较长的变量会转存到老生代,老生代占据几乎所有内存,64位下大概1400MB

memory-structure-of-V8.png 【译】了解 V8 内存管理

新生代

新生代内存分为两个相等大小的Semi-space,分别为From-Space和To-Space。From-Space是真正使用的内存,To-Space是空闲的,GC是才会用到。

Minor GC使用了清道夫算法Scavenge,其实现又使用了 Cheney 算法:

  1. 广度优先遍历From-Space中对象,将存活对象复制到To-space
  2. 遍历完成,清空From-Space
  3. From-Space和To-Space角色互换

复制-清空,不需要进行磁盘整理,提升回收速度,牺牲空间换时间

新生代内存空间小,牺牲一半空间问题不大,老生代就不能这样做了,不然空间浪费太多

新生代的对象转移到老生代称为晋升 Promote。

老生代

老生代内存分为两部分,指针区(Old Pointer Space),数据区(Old Data Space)。如果对象可能有指向其他对象的指针,存在指针区,大多数晋升的对象都在这里。数据区只保存原始对象,没有指向其他对象的指针。

Major GC使用两种算法:标记清除(Mark-Sweep)、标记整理(Mark-Compact)

Major GC流程:

  • Marking 标记,标记已死变量
  • Sweeping 清除,清除已死变量
  • Compaction/Compacting 整理磁盘 -- 清理后内存断续,让内存连续(整理磁盘耗时)

垃圾回收

触发回收

  1. 执行完一次代码
var a = 123;
var b = 1;
console.log(a);
setTimeOut(() => {
    b ++;
    console.log(b);
    // 回收1次
}, 2000);
// 回收1次
  1. 内存不够时
const setArray = () => {
    // 设置一个超大长度的数组
    var size = 30 * 1024 * 1024;
    let array = new Array(size);
    // 不停地往数组中塞值,这将引起内存花销的迅速增大
    for (let i = 0; i < size; i ++) {
        array[i] = 0;
    }
}
// 连续设置8个上面所示的超大数组
for (let j = 0; j < 8; j++) {
    setArray();
}

js避免报出内存不够,会把能回收的回收了

判断变量可以回收

  1. 全局变量,直到程序执行完毕才会回收
  2. 普通变量,局部变量,当他们失去引用时可以回收

新生代老生代如何转化

  1. 新生代发现,从from复制到to空间后会占用25%以上to空间,就会将一些变量放到老生代
  2. 放已经经历过一次回收,但是还没死的变量放老生代

如何优化内存

检测内存

浏览器端:window.performance.memory 仅看当前这种状态的内存,不能看一个过程的。单位bit,转MB要/1024/1024

Node端:process.memoryUsage()

image-20220215151542626.png

  • rss:node总占用内存(c++、v8引擎总量)
  • heapTotal: v8引擎总内存
  • heapUsed: v8引擎使用内存
  • external: 当前使用内存中c++分配给v8的额外内存
  • arrayBuffers: 1.4版本后node新特性,arrayBuffers占用的内存

内存优化建议

  1. 尽量不要定义全局变量,定义了及时手动释放(全局变量要等执行结束才能回收)

    手动释放:全局变量 = undefined / null

  2. 注意闭包

var fn = (function () {
    var arr = [];
    return function() {
        arr.push(new Array(30 * 1024 * 1024));
    }
})();

arr这个局部变量会一直在,外部有个方法引用着这个局部变量。有引用时不会被回收,除非fn这个方法被回收了。

使用闭包时注意看闭包是否有无限增长的情况。

Node端一些特殊点

Node可以手动触发垃圾回收 - global.gc

Node端可以设置内存 - node --max-old-space-size=1700 test.jsnode --max-new-space-size=1024 test.js

为什么v8要设计为1.4G

  1. 1.4g对于浏览器脚本本来就够用

  2. 回收的时候是阻塞式的,也即进行垃圾回收时会中断代码的执行