内存管理
减少浏览器负担 -- 内存过大会导致浏览器压力过大 -- 浏览器卡顿
内存不够 -- 服务中断
内存数据存储
内存分类
- 栈内存,先进后出,线性连续的数据结构,
- 堆内存,树状结构,非线性不连续的数据结构
变量存储
- 普通类型变量,直接存在栈里,值和变量本身存在一起,变量直接指向值
- 方法、对象、数组等引用类型,在堆内存开创地址储存对象本身,将它赋值给一个变量后,就在栈内存存地址(在栈内存加入这个变量,让这个变量指向堆内存中对应的地址,而不是变量本身)
V8内存管理
V8限制了所有能用的内存极限,64位下是1.4G,32位下700M,根据浏览器不同有些许扩容,Node情况下会有C++内存扩容
v8内存构成:新生代内存区,老生代内存区,大对象区,代码区,Map区
- 新生代,短时间存活的新变量存在新生代中,新生代内存极小,64位下大概32MB
- 老生代,生存时间较长的变量会转存到老生代,老生代占据几乎所有内存,64位下大概1400MB
新生代
新生代内存分为两个相等大小的Semi-space,分别为From-Space和To-Space。From-Space是真正使用的内存,To-Space是空闲的,GC是才会用到。
Minor GC使用了清道夫算法Scavenge,其实现又使用了 Cheney 算法:
- 广度优先遍历From-Space中对象,将存活对象复制到To-space
- 遍历完成,清空From-Space
- From-Space和To-Space角色互换
复制-清空,不需要进行磁盘整理,提升回收速度,牺牲空间换时间
新生代内存空间小,牺牲一半空间问题不大,老生代就不能这样做了,不然空间浪费太多
新生代的对象转移到老生代称为晋升 Promote。
老生代
老生代内存分为两部分,指针区(Old Pointer Space),数据区(Old Data Space)。如果对象可能有指向其他对象的指针,存在指针区,大多数晋升的对象都在这里。数据区只保存原始对象,没有指向其他对象的指针。
Major GC使用两种算法:标记清除(Mark-Sweep)、标记整理(Mark-Compact)
Major GC流程:
- Marking 标记,标记已死变量
- Sweeping 清除,清除已死变量
- Compaction/Compacting 整理磁盘 -- 清理后内存断续,让内存连续(整理磁盘耗时)
垃圾回收
触发回收
- 执行完一次代码
var a = 123;
var b = 1;
console.log(a);
setTimeOut(() => {
b ++;
console.log(b);
// 回收1次
}, 2000);
// 回收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避免报出内存不够,会把能回收的回收了
判断变量可以回收
- 全局变量,直到程序执行完毕才会回收
- 普通变量,局部变量,当他们失去引用时可以回收
新生代老生代如何转化
- 新生代发现,从from复制到to空间后会占用25%以上to空间,就会将一些变量放到老生代
- 放已经经历过一次回收,但是还没死的变量放老生代
如何优化内存
检测内存
浏览器端:window.performance.memory 仅看当前这种状态的内存,不能看一个过程的。单位bit,转MB要/1024/1024
Node端:process.memoryUsage()
- rss:node总占用内存(c++、v8引擎总量)
- heapTotal: v8引擎总内存
- heapUsed: v8引擎使用内存
- external: 当前使用内存中c++分配给v8的额外内存
- arrayBuffers: 1.4版本后node新特性,arrayBuffers占用的内存
内存优化建议
-
尽量不要定义全局变量,定义了及时手动释放(全局变量要等执行结束才能回收)
手动释放:全局变量 = undefined / null
-
注意闭包
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.js 和 node --max-new-space-size=1024 test.js
为什么v8要设计为1.4G
-
1.4g对于浏览器脚本本来就够用
-
回收的时候是阻塞式的,也即进行垃圾回收时会中断代码的执行