内存控制
-
V8的垃圾回收机制 / 内存限制
- V8让JS虚拟机的性能达到了很快的地步,所以node实现在V8上
- V8的内存限制:Node中通过JS使用内存只能使用部分内存(64G下大概1.4GB),所以Node无法直接操作大内存对象
- V8的对象分配
- V8中,所有JS对象都是通过堆进行分配,如果已申请的堆空闲不够分配新的对象,将继续申请堆内存
- 通过process.memoryUsage()可以看到现在的内存使用情况
- Node在启动的时候可以传递 --max-old-space-size / --max-new-space-size 来调整内存限制的大小,只在初始化有效。
- V8 的垃圾回收机制
- V8主要的垃圾回收算法:分代式垃圾回收机制
- V8中将内存分为新生代和老生代,新生代的对象存活时间短,老生代是存存活活时间长/常驻内存的对象
- 新生代中的对象主要通过scavenge算法进行垃圾回收,而在scavenge的具体实现使用了Cheney算法
- 将对内存一分为二,每一部分空间成为semi space。
- 2个semispace只有一个在使用,一个处于闲置状态。处于使用状态的semi space空间称为from空间,处于闲置状态的空间成为To空间。
- 分配对象的时候,先从from空间中开始分配
- 垃圾回收时,会检查from空间中的存活对象,存活对象呗复制到To空间,不存活的对象占用的空间将会被释放。
- to和from空间 交换
- 总之,这个算法的意思就是将存活对象在两个semi space空间中进行复制/翻转
- 这个算法牺牲了空间,换取时间;之所以采用这个算法是因为新生代中对象的生命周期很短,占用的内存也少
- 当一个对象经过多次复制和转换依然存活,它会被认为是生命周期较长的对象,从而晋升到老生代中。而晋升的套件一般是2个
- 对象是否经历过scavenge回收,如果有,就晋升
- To空间的内存占用比是否超过比例,如果超过,直接晋升
- 老生代中常用Mark - Sweep(标记清除)& Mark - compact(标记整理)方法
- mark-sweep 标记清除
- 遍历每个对象,并标记存活的对象,清楚阶段只清楚没有被标记的对象。
- 缺点:很容易出现清理之后内存空间不连续,这种内存碎片对后续的内存分配造成问题,因为很可能出现需要分配一个打碎片,所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收
- mark-compact 标记整理
- 标记每个对象,将活着的对象往一端移动,移动完成之后,直接清理掉边界外的内存。
- incremental Marking 增量标记,减少垃圾回收的停顿时间
- 因为每次回收的时候,JS会阻塞,所以把大的垃圾回收工程拆分一小步一小步进行,比如增量标记和增量清理,还有延迟清理(lazy sweeping)
- mark-sweep 标记清除
- V8主要的垃圾回收算法:分代式垃圾回收机制
- 查看垃圾回收日志
- 启动node的时候添加 --trace-gc参数
-
高效使用内存
- 作用域退出/不再使用了 就会释放这个作用域里的变量
- 变量的主动释放
- 如果变量是全局变量,对象将常驻在内存。删除手段有
- delete操作删除引用关系
- 重新赋值,让旧对象脱离引用关系/ 其实直接设置成空/null不行吗
- 如果变量是全局变量,对象将常驻在内存。删除手段有
- 闭包(closure)
- 闭包导致闭包自身作用域不得到释放
- 变量的主动释放
- 作用域退出/不再使用了 就会释放这个作用域里的变量
-
内存指标
- 查看内存使用情况:通过process.memoryUsage()可以看到现在的内存使用情况
- 查看进程的内存使用,process.memoryUsage()
- 查看系统的内存占用,os模块的totalmem() 和 freemem() 用来查看系统的总内存和限制内存
- 堆外内存:我们把不是通过V8分配的内存叫做 堆外内存
-
内存泄漏
- 造成内存泄漏的原因常见的有
- 缓存
- 队列消费不及时
- 作用域未释放
- 内存 != 缓存,慎重
- 一旦命中缓存,就可以节省一次I/O的时间;但是一个对象被当作缓存使用,意味着它常驻在老生代中;缓存中存储的键越多,长期存活的对象也就越多,那么垃圾回收旧会做无用功
- 缓存限制策咯:限制缓存的无限增长
- 作者写过一个limitablemap模块——P128
- 缓存的解决方案
- 直接将内存作为缓存的方案,除了缓存大小的顾虑外,还要考虑进程之间无法共享内存,进程内使用缓存将导致缓存不可避免的有重复,浪费物理空间
- 解决方案:采用进程外的缓存,进程自身不存储状态
- 将缓存转移到外部,减少常驻内存的对象的数量,让垃圾回收变得高效
- 进程之间可以共享缓存
- 常见的缓存方案( 有客户端)
- Redis
- Memcached
- 关注队列状态,因为也有可能造成内存泄漏
- 造成内存泄漏的原因常见的有
-
内存泄漏排查
- 常见工具:v8-profiler(3年没维护了),node-heapdump,node-mtrace,dtrace,node-memwatch
-
大内存应用
-
使用stream模块处理大文件(由于V8内存限制,我们无法通过fs.readFile()和fs.writeFile()直接对大文件进行操作)
-
使用fs.createReadStream() / fs.createWriteStream()方法通过流的方式实现对大文件的操作
-
var reader = fs.createReadStream('in.txt'); var writer = fs.createWriteStream('out.txt'); reader.on('data',function(chunk){ writer.write(chunk); }) reader.on('end',function(){ writer.end; }) //利用es6 中的pipe,简写后 var reader = fs.createReadStream('in.txt'); var writer = fs.createWriteStream('out.txt'); reader.pipe(writer); 复制代码
-
-