Nodejs memory内存管理

ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(乐观的人在每个危机里看到机会,悲观的人在每个机会里看见危机。——邱吉尔)
ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ在这里插入图片描述

什么是内存?
这是一个特别宽泛的问题,每一个编程领域的都可能会有不同的回答,尤其是对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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值