5内存控制
一个是垃圾回收:原理、如何查看;一个是内存:进程内存、堆外内存、内存泄漏原因以及排查等
1、V8的垃圾回收机制与内存限制
v8内存限制大小,为什么限制、垃圾回收机制、以及如何查看垃圾回收日志
- 1、Node与v8
- Lars Bak 开发高性能的虚拟机工作背景
- 无与伦比的经历让V8一出世就超越了当时所有的JavaScript虚拟机。
- 2、v8的内存限制
- 64位系统下约为1.4GB,32位系统下约为0.7 GB
- V8为何限制了内存的用量?
- 内存使用量的查看方式bash
node > process.memoryUsage(); { rss: 14958592, heapTotal: 7195904,// v8申请到堆内存 heapUsed: 2821496 }// 当前使用量
- 表层原因:为V8最初为浏览器而设计,不太可能遇到用大量内存的场景。
- 深层原因:
- 是V8的垃圾回收机制的限制。
- 内存越大垃圾回收时间越多
- V8做一次小的垃圾回收需要50毫秒以上,
- 做一次非增量式的垃圾回收甚至要1秒以上
- 在当时的考虑下直接限制堆内存是一个好的选择。
- 使用更多内存js
node --max-old-space-size=1700 test.js // 单位为MB(老生代) // 或者 node --max-new-space-size=1024 test.js // 单位为KB(新生代)
- 我曾在自己的项目中遇到过,服务器内存过小,导致vite启动打包失败
- 3、V8的垃圾回收机制
- v8内存分代
- 新生代
- 存活时间较短的对象
- 两个reserved_semispace_size_所构成
- 老生代
- 较长、常驻
- 新生代
- v8的垃圾回收算法
- Scavenge算法(新生代、时间少)
- 采用 Cheney算法
- 内存一分为二, 每一部分空间称为semispace,
- 只有一个处于使用中(From空间), 另一个处于闲置状态(To空间)
- 垃圾回收时, 会检查From中的存活对象,并且被被复制到To中,非存活对象占用的空间将会被释放。
- 完成复制后, From空间和To空间的角色发生对换。(翻转)
- 当一个对象经过多次复制依然存活时,生命周期较长,随后会被移动到老生代中(晋升)
- 晋升两个条件:对象是否经历过Scavenge回收, 一个是To空间的内存占用比超过限制(超过25%)
- Mark-Sweep & Mark-Compact
- Mark-Sweep 标记清除
- 标记活着对象和清除没有被标记的对象
- 问题:内存碎片、不连续状态、不好分配大对象
- Mark-Compact 标记整理
- 解决sweep问题
- 对象在标记为死亡后,
- 在整理的过程中,将活着的对象往一端移动,移动完成后,
- 直接清理掉边界外的内存。
- v8中两种方法结合使用
- Mark-Sweep 标记清除
- Incremental Marking 增量标记
- 以上三种算法执行时,应用逻辑暂停,为“全停顿”(stop-the-world)
- 老生代影响较大
- 解决:一口气停顿完成的动作,拆分为小“步进”
- 做一“步进”停下,就让应用逻辑执行,交替执行
- 延迟清理(lazy sweeping) 与增量式整理(incremental compaction)
- Scavenge算法(新生代、时间少)
- v8内存分代
- 4、查看垃圾回收日志
--trace_gc 看耗时
- node --trace_gc -e "var a = [];for (var i = 0; i < 1000000; i++) a.push(new Array(100))"
- 找出垃圾回收的哪些阶段比较
耗时
--prof
- node --prof test01.js(5.1.js) //放一个1000000的for循环
jsfor (var i = 0; i < 1000000; i++) { var a = {}; }
- 得到一个v8.log日志文件,不具备可读性
- linux-tick-processor v8.log
- windows-tick-processor.bat v8.logjs
// 垃圾回收所占的时间为5.4% [GC]: ticks total nonlib name 2 5.4%
2、高性能使用内存
了解作用域、闭包以更好的使用内存
- 1、作用域 scope
- 2、闭包
js
// 一旦有变量引用这个中间函数, 这个中间函数将不会释放, 同时也会使原始的作用域不会得到释放,
var foo = function () {
var bar = function () {
var local = "局部变量";
return function () {
return local;
};
};
var baz = bar();
console.log(baz());
};
3、内存指标
进程内存(v8堆内存)以及如何查看、堆外内存 1、查看内存使用
查看进程内存
- process.memoryUsage()(5.3.1.js)
jsvar showMem = function () { var mem = process.memoryUsage(); var format = function (bytes) { return (bytes / 1024 / 1024).toFixed(2) + " MB"; }; console.log( "Process: heapTotal " + format(mem.heapTotal) + " heapUsed " + format(mem.heapUsed) + " rss " + format(mem.rss) ); console.log("--------------------------------------------------------"); }; var useMem = function () { var size = 20 * 1024 * 1024; var arr = new Array(size); for (var i = 0; i < size; i++) { arr[i] = 0; } return arr; }; var total = []; for (var j = 0; j < 15; j++) { showMem(); total.push(useMem()); } showMem();
- rss是resident set size的缩写, 即进程的常驻内存部分
- heapTotal 1367.99 MB heapUsed 1361.86 MB rss 1375.00 MB
查看系统内存
- os模块中的totalmem()和freemem()
jsnode > os.totalmem() 8589934592 > os.freemem() 4527833088 >
2、堆外内存(5.3.2.js)
- 使用buffer做测试案例
- Process: heapTotal 5.85 MB heapUsed 1.85 MB rss 3012.91 MB
- heapTotal与heapUsed的变化极小,唯一变化的是rss的值
- Buffer对象,它不经过V8的内存分配机制,不会有堆内存的大小限制。
4、内存泄漏
内存泄漏会出现在哪些方面:缓存、队列、作用域,以及解决方案
缓存
队列消费不及时
作用域未释放
1、慎将内存当做缓存
- 常用对象的键值对来缓存东西
- 加上完善的过期策略以防止内存无限制增长,可以使用
- 缓存限制策略,
- 加一种策略限制无限增长,limitablemap一旦超出就先进先出淘汰
- 模块机制中,模块编译后都会缓存,写模块时避免对象/数组无限制增长,添加清空队列的相应接口
- 缓存解决方案
- 将缓存转移到外部:Redis、Memcached
- 进程之间可以共享缓存。
2、关注队列状态(数组)
- 日志收集
- 表层解决方案:是换用消费速度更高的技术,换数据库为文件写入
- 深度:监控队列长度,一旦堆积、报警;任意异步调用包含超时机制
- 日志收集
5、内存泄漏排查
内存泄漏排查工具,如何使用
- node-heapdump
- node-memwatch
- 模块较久更新,现在应该有更新的排查工具
6、大内存应用
如何处理大文件:stream pipe
stream模块处理大文件(5.6.js)
jsconst fs = require("fs"); // 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(); // }); // or var reader = fs.createReadStream("in.txt"); var writer = fs.createWriteStream("out.txt"); reader.pipe(writer);
fs的createReadStream()和createWriteStream()
管道方法pipe()
现在有功能更丰富的api,查阅node对应文档
实际案例回顾
- 背景:
- 项目(直播平台),为了更好地seo,从前端项目升级为node项目,使用nuxt框架,单页面升级到ssr,
- 代码迁移工作:
- nuxt支持vue,将之前代码js vue vuex状态管理,一并迁移,
- 未更新vuex状态管理使用方法,
- 导致数据错乱,一个直播间,会闪现别的直播间的数据
- 为什么未更新vuex状态管理使用方法就导致这个问题?
- 分析:
- 1、由原前端浏览器环境,变为node服务器环境,对于访问用户而言,原来这套js每个浏览器下载一遍,数据不共用,而现在服务器端这套js大家是共用的;
- 2、vue状态管理的代码逻辑是,将主要的数据存储到了对象,这个对象大家是共用的,当多个用户大量访问,延时未及时更新时,就会出现错乱现象;
- 3、更新vuex的使用方法,vuex中存储的对象,改为函数调用返回一个新对象,做到数据的隔离,则问题解决