V8的垃圾回收机制与内存限制
一般的后端开发语言中,基本的内存使用上没有什么限制,然而在Node中通过Javascript使用内存时就会发现只能使用部分内存(64位系统中约为1.4GB,32位系统中约为0.7GB)。这样限制,会导致2GB的文件读取内存无法进行字符串分析处理,即使物理内存有32GB,在Node单进程中,计算机的内存资源无法得到充分的使用。
背后核心的问题在于Node基于V8构建,Node中使用的JS对象基本上都是通过V8的方式分配和管理,V8在浏览器足够使用,但是在后台服务器开发中却无法满足需求。
查看内存使用情况
node
precess.memoryUsage()
V8的垃圾回收机制
V8的垃圾回收算法
主要是基于分代垃圾回收机制,现在的垃圾回收算法中按照对象的存活事件将内存的垃圾回收进行不同的分代,然后对不同分代的内存施以更高效的算法。
-
V8的内存分代
主要将内存分为新生代和老生代两代。新生代中的对象位存活时间较短的对象,老生代中的对象位存活时间较长或者常驻内存的对象。
内存的设置需要在Node启动的时候设置,无法动态改变
--max-new-space-size 1024 单位MB --max-old-space-size 1024 单位KB
默认设置下,在64位系统和32位系统下只能使用越1.4GB和约0.7GB的大小
-
Scavenge算法
在分代的基础上,新生代中的对象主要通过Scanvenge算法进行垃圾回收,具体是一种采用复制的方式实现的垃圾回收算法,它将堆内存一分为二,两个空间,只有一个处于使用中,另外处于闲置状态,处于使用状态的空间位From空间,处于闲置状态的空间称为To空间。当分配对象时,首先在From空间进行分配,当开始进行垃圾回收,会检查From空间中的存活对象,存活对象将被复制到To空间,非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色进行兑换。
Scavenge的缺点是只能使用堆内存中的一半,典型的牺牲了空间换取时间的算法,非常适合新生代对象的生命周期较短。
当一个对象经过多次复制依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中
-
Mark-Sweep & Mark-Compact
对于老生代中的对象,由于存活对象占较大比重,所以采用标记清楚,分为标记和清除两个阶段,在标记阶段遍历堆中的虽有对象,并标记活着的对象,在随后的清除阶段,只清除没有被标记的对象。为了解决清除后出现的内存碎片问题,出现Mark-Compact
回收算法 Mark-Sweep Mark-Compact Scavenge 速度 中等 最慢 最快 空间开销 少(碎片) 少(无碎片) 双倍空间(无碎片) 是否移动对象 否 是 是
高效使用内存
作用域
JS的作用域只有函数调用,以及全局作用域
-
函数调用
函数在每次被调用会创建对象的作用域,函数执行结束,该作用域将会销毁,同事作用域中声明的局部变量分配在该作用域上,随着作用域的销毁而在下次垃圾回收时被释放
-
变量的主动释放
如果变量私全局变量,由于全局作用域需要直接到进程退出才能释放,此时将导致引用的对象常驻内存,此时如果需要释放常驻内存的对象,可以将变量重新赋值null defined即可,接下来的老生代内存清楚和整理的过程中,会被回收释放。
-
闭包
闭包的使用会导致,一旦变量引用了这个中间函数,这个中间函数就不会释放,同时也会使原始的作用域不会得到释放,除非不再引用,才会逐步释放。
内存的划分
-
栈内内存
主要是通过V8的划分获得 主要受V8引擎的限制。
-
堆外内存
主要是Stream Buffer之类的需要处理二进制的数据 在堆外划分,主要是受操作系统的进程常驻内存。
大内存应用
Node主要提供了stream模块用于处理大文件
stream模块是Node的原生模块, stream继承EventEmitter,对于大文件我们无法通过fs.readFile()和fs.writeFile()直接进行大文件的操作,可以通过改用流的方式对大文件的操作
var reader = fs.createReadStream('in.txt')
var writer = fs.createWriteStream('out.txt')
reader.pipe(writer);