本文为读书笔记。
一、V8的垃圾回收机制与内存限制
Node使用chrome的V8作为JS脚本引擎,因此Node的内存管理与V8关系很密切。
1.V8的内存限制
2.V8的对象分配
至于V8为何要限制堆的大小,表层原因为V8最初为浏览器而设计,不太可能遇到用大量内存的场景。对于网页来说,V8的限制值已经绰绰有余。深层原因是V8的垃圾回收机制的限制。按官方的说法,以1.5 GB的垃圾回收堆内存为例,V8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。这是垃圾回收中引起JavaScript线程暂停执行的时间,在这样的时间花销下,应用的性能和响应能力都会直线下降。这样的情况不仅仅后端服务无法接受,前端浏览器也无法接受。因此,在当时的考虑下直接限制堆内存是一个好的选择。”
3.垃圾回收机制
增量标记
为了降低全堆垃圾回收带来的停顿时间,V8先从标记阶段入手, 将原本要一口气停顿完成的动作改为增量标记(incremental marking),也就是拆分为许多小“步进”,每做完一
4.堆外内存
Node中的内存使用并非都是通过V8进行分配的。我们将那些不是通过V8分配的内存称为堆外内存。
Buffer对象不同于其他对象,它不经过V8的内存分配机制,所以也不会有堆内存的大小限制。
这意味着利用堆外内存可以突破内存限制的问题。
为何Buffer对象并非通过V8分配?这在于Node并不同于浏览器的应用场景。在浏览器中,JavaScript直接处理字符串即可满足绝大多数的业务需求,而Node则需要处理网络流和文件I/O流,操作字符串远远不能满足传输的性能需求。
二、内存泄露
- 缓存
- 队列消费不及时
- 作用域未释放
1.慎用内存做缓存
2.队列的问题
3.就是闭包的问题。
三、大内存应用
四、理解Buffer
1.基本用法
Buffer对象类似于数组,它的元素为16进制的两位数,即0到255的数值。示例代码如下所示:
var str = "深入浅出node.js";
var buf = new Buffer(str, 'utf-8');
console.log(buf);
// => <Buffer e6 b7 b1 e5 85 a5 e6 b5 85 e5 87 ba 6e 6f 64 65 2e 6a 73>
由上面的示例可见,不同编码的字符串占用的元素个数各不相同,上面代码中的中文字在UTF-8编码下占用3个元素,字母和半角标点符号占用1个元素。
Buffer受Array类型的影响很大,可以访问length属性得到长度,也可以通过下标访问元素,在构造对象时也十分相似,代码如下:
var buf = new Buffer(100);
console.log(buf.length); // => 100
上述代码分配了一个长100字节的Buffer对象。可以通过下标访问刚初始化的Buffer的元素,代码如下:
console.log(buf[10]);
这里会得到一个比较奇怪的结果,它的元素值是一个0到255的随机值。
值得注意的是,如果给元素赋值不是0到255的整数而是小数时会怎样呢?示例代码如下所示:
buf[20] = -100;
console.log(buf[20]); // 156
buf[21] = 300;
console.log(buf[21]); // 44
buf[22] = 3.1415;
console.log(buf[22]); // 3
给元素的赋值如果小于0,就将该值逐次加256,直到得到一个0到255之间的整数。如果得到的数值大于255,就逐次减256,直到得到0~255区间内的数值。如果是小数,舍弃小数部分,只保留整数部分。
2.内存分配
为了高效地使用申请来的内存,Node 采用了slab分配机制。slab是一种动态内存管理机制,最早诞生于SunOS操作系统(Solaris)中,目前在一些*nix操作系统中有广泛的应用,如FreeBSD和Linux。
full:完全分配状态。
partial:部分分配状态。
empty:没有被分配状态。
当我们需要一个Buffer对象,可以通过以下方式分配指定大小的Buffer对象:
new Buffer(size);
Node以8 KB为界限来区分Buffer是大对象还是小对象:
Buffer.poolSize = 8 * 1024;
1.分配小对象
var pool;
function allocPool() {
pool = new SlowBuffer(Buffer.poolSize);
pool.used = 0;
}
在图6-2中, slab处于empty状态。
构造小Buffer对象时的代码如下:
new Buffer(1024);
这次构造将会去检查pool对象,如果pool没有被创建,将会创建一个新的slab单元指向它:
if (!pool || pool.length - pool.used < this.length) allocPool();
this.parent = pool;
this.offset = pool.used;
pool.used += this.length;
if (pool.used & 7) pool.used = (pool.used + 8) & ~7;
2.分配大Buffer对象
// Big buffer, just alloc one
this.parent = new SlowBuffer(this.length);
this.offset = 0;
这里的SlowBuffer类是在C++中定义的,虽然引用buffer模块可以访问到它,但是不推荐直接操作它,而是用Buffer替代。
上面提到的Buffer对象都是JavaScript层面的,能够被V8的垃圾回收标记回收。但是其内部的parent属性指向的SlowBuffer对象却来自于Node自身C++中的定义,是C++层面上的Buffer对象,所用内存不在V8的堆中。