内存和垃圾回收
1. v8内存大小限制
在Node中如果通过JavaScript使用内存操作时会发现实际只能使用部分内存(64位系统下约为1.4G,32位系统下约为0.7G),这种限制对于其他的服务端开发语言来说基本上都是不存在的。
- 和操作系统有关:64位为1.4G,32位为0.7G
- 64位下新生代的空间为64MB,老生代为1400MB
- 32位下新生代的空间为16MB,老生代为700MB 而V8的这种限制导致的结果是Node无法直接操作大内存对象。在单个Node进程的情况下,计算机的内存资源无法得到充足的使用。
而问题的原因在于Node是基于V8构建,所以在Node中使用对象都是通过V8自己的方式进行分配和管理。
而其内存管理机制在浏览器的场景下问题不大,但是对于Node,却使得Node有了这般限制。
为什么?
- JavaScript 是脚本语言,它不像c,java,php一样有很多内容持久性的保存在内存中,脚本语言只执行一次,执行完毕就会释放内存,1.4G足够用。
- 如果不给限制会有什么问题。v8回收一次垃圾 100mb => 3ms, 看似3ms没什么问题,但是有个问题是,v8在回收的时候是暂停所有代码执行的。
2. v8的内存分配
新生代和老生代
- 所谓新生代,指的是新产生的对象;
- 老生代就是经历过新生代垃圾回收后还“存活”下来的对象。
新生代 => 老生代
- 这个变量经历过内存回收。
- 新生代内存一旦使用了25%。
新生代垃圾回收算法 Scavenge GC:
- 我们把新生代对象的内存平均分开 2 份空间From 和 To
- 每当有新生对象诞生,就会在 From 空间出现
- 一旦 From 空间被占满,就触发 Scavenge GC
- 从 From 空间拿出存活的对象,复制到 To 空间
- 清空 From 空间 (这样就可以实现把不活跃的对象给回收掉)
- From To 空间角色互换,开始下一轮循环
还有一种情况,当复制到 To 空间的时候, To 空间已经使用了25%,那么这个对象直接晋升到老生代区。
老生代垃圾回收算法
老生代中用标记 - 清除(Mark-Sweep)的算法来处理。
首先是标记过程阶段,标记阶段就是从一组根元素开始,递归遍历这组根元素(遍历调用栈),在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据.然后在遍历过程中标记,标记完成后就进行清除过程。它和副垃圾回收器的垃圾清除过程完全不同,这个的清除过程是删除标记数据。
清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了标记 - 整理(Mark-Compact)算法,这个标记过程仍然与标记 - 清除算法里的是一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,从而让存活对象占用连续的内存块。
3. 内存如何回收
;
查看内存
- 浏览器 window.performance;
- Node process.memoryUsage();
容易引发内存使用不当的情景
- 滥用全局变量
- 使用后及时释放内存,将变量赋值为undefined或者null;
- 缓存不限制
- 缓存限制内存大小
- 操作大文件
- 断点续传,切片上传
如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。 const arr = [1,2,3,4]; console.log(“hello world”); 上面的代码中,数组[1,2,3,4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它是会持续占用内存。
为了确保有效的回收内存,应该及时解除不再使用的全局对象,全局对象属性以及循环引用变量的引用。
不如来个demo模拟一下js内存回收的过程:
function getMemory() {
// 获取内存信息
var mem = process.memoryUsage();
// 格式化内存,转换为mb
var format = function(bytes) {
return (bytes/1024/1024).toFixed(2) + 'MB';
}
console.log('heapTotal: ' + format(mem.heapTotal) +
' heapUsed: ' + format(mem.heapUsed));
console.log('******************************************************');
// 声明数组a,作为存储容器
var a = [];
var size = 30*1024*1024;
// 通过函数b声明几个大内存的变量
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);
var arr6 = new Array(size);
}
b();
// 每个一秒往容器a中存入一个大数组,输出当前内存的信息
setInterval(() => {
a.push(new Array(size));
getMemory();
}, 1000)
}
执行上面代码会发生什么事情呢?
可以看出在不断往a里面添加数组的时候,占用的内用不断增大,但中间有一个过程内存明显下降,然后继续增大,直到内存不够报错。这个阶段就是js内存回收,后面发现没有可以释放的内存了,内存不够了就只能报错。