ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(乐观的人在每个危机里看到机会,悲观的人在每个机会里看见危机。——邱吉尔)
ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
什么是内存?
这是一个特别宽泛的问题,每一个编程领域的都可能会有不同的回答,尤其是对Nodejs这种提供JS运行时的平台来说,稍有不慎就可能造成内存溢出。
在node中,64位系统只能使用1.4g内存,而32位系统只能使用0.7g的内存,这样的机制对前端来说是绰绰有余,但对于服务端来说,就是一个限制的枷锁
v8虚拟机
https://blog.csdn.net/qq_42427109/article/details/105336108
垃圾回收机制
https://blog.csdn.net/qq_42427109/article/details/100902835
示例代码
该代码段将演示,Nodejs在内存管理上的流程和垃圾回收机制
/**
* @event Nodejs内存管理
* @event Nodejs垃圾回收机制 https://blog.csdn.net/qq_42427109/article/details/100902835
*/
// toFixed JavaScript内置的四舍五入方法 参数为保留的小数点,默认为0
const format = (data) => (data / 1024 / 1024).toFixed(2) + "MB";
/**
* @event 封装prints方法
* @description
* @param rss(resident set size) RAM 中保存的进程占用的内存部分,包括代码本身、栈、堆
* @param heapTotal 堆中总共申请到的内存量
* @param heapUsed 堆中目前用到的内存量,判断内存泄漏我们主要以这个字段为准
* @param external V8 引擎内部的 C++ 对象占用的内存
*/
const prints = () => {
const memory = process.memoryUsage();
console.log(
JSON.stringify({
rss: format(memory.rss),
heapTotal: format(memory.heapTotal),
heapUsed: format(memory.heapUsed),
external: format(memory.external)
})
);
};
prints();
const size = 20 * 1024 * 1024;
let array = [];
const fors = () => {
array = new Array(size);
for (let index = 0; index < size; index++) {
array[index] = 0;
}
return array;
}
/**
* @descript v8主要使用mark-sweep(标记清除)方法进行垃圾回收,当内存不足时再使用mark-compact(标记整理)清除内存碎片
*/
for (let index = 0; index < 20; index++) {
const total = [];
total.push(fors());
prints();
// global.gc();
}
运行该JS代码得到结果
可以看到,我们循环了20次,每次声明一个新的数组(total),并调用fors函数,声明20 * 1024 * 1024长度的数组进行遍历,再将返回的结果放入total数组中,重复执行此动作20次。
但我们可以看到,内存占用空间呈现一个递增的状态,且每次递增的值heapUsed在160m左右,如果像v8垃圾回收所描述的那样,边回收边运行的话,为什么内存一直居高不下,直到堆中的内存达到1283.57m时才下降呢?
查看垃圾回收日志
使用node --expose-gc --trace_gc file > gc.log 命令可以查看每次GC时的时间、类型、堆大小变化和产生原因,并导出到gc.log文件中
从打印的gc日志中看到,该代码在执行时首先执行了两次Scavenge之后执行了Mark-sweep(标记清除)。并且我们可以看到在日志中有incremental字眼,意思为增量。增量标记就是v8默认采取的垃圾回收机制,边回收边运行,优化了因垃圾回收造成程序暂停的影响
该日志从上往下看,在"heapUsed":"964.35MB"时,v8进行了一次Mark-sweep的gc操作,将堆中的内存从1124回收至963,并继续执行程序,直到**“heapUsed”:"1283.62MB"时,再一次进行了回收,但这次的回收的信息和之前的不同,它出现了allocation failure GC in old space requested**(gc分配失败),原因是老轻代剩余空间不足以分配新的数据,导致触发了full gc(清理整个年轻和老年代空间)
现在我们在运行一下代码,不再每次创建新对象,而是在for循环外声明,让它无法被回收
const total = [];
for (let index = 0; index < 20; index++) {
total.push(fors());
prints();
// global.gc();
}
很明显,因为total常驻在老年代,无法被回收,且每次声明20 * 1024 * 1024长度的数组并循环,从而造成内存溢出
在日志中我们看到,由于v8采用的增量标记导致堆内存达到1283m时触发了full gc
由于total常驻老年代,无法被释放,触发full gc分配释放失败,v8则再尝试一次full gc分配,如果第二次 GC 后依然无法分配出足够的内存,V8 会进行一次更彻底的 GC,在回收弱引用的时候(弱引用的对象不介意何时被 GC 回收,在计算可达性的时候弱引用不算可达),强制触发相关的 GC 回调,这次 GC 的触发原因在日志里叫做 last resort gc(v8最后的手段) 。如果这次 GC 后依然分配失败,V8 将会由于进程内存用尽(process out of memory)退出
参考文章
https://blog.csdn.net/yunqiinsight/article/details/89138640 //Nodejs内存故障排查
https://www.cnblogs.com/leeego-123/p/11298267.html // full gc
http://www.360doc.com/content/17/0905/09/43931101_684690738.shtml // v8 gc