关于node.js:node的垃圾回收

40次阅读

共计 4993 个字符,预计需要花费 13 分钟才能阅读完成。

node 的内存限度:

  1. 内存限度和起因:

对于 node 来说,内存始终是限度 node 在后端宽泛深度应用的起因,因为 node 的 v8 引擎能从服务器的内核中调配进去的内存不多:

对于 32 位的零碎,v8 调配进去的内存个别只有 0.7g;

对于 64 位零碎,v8 调配进去的内存个别只有 1.4g;

因而如果将一个比拟大的内容存储在内容或者一个存储在内存的体积超过了对应零碎 v8 的下限,就会呈现 out of memory,从而退出 node 过程(单过程)。

外表起因:是因为 v8 岂但要应用在服务端环境,还须要在浏览器环境下应用,所以为了衡量两者对内村的须要,只能失去上述的 v8 内存限度;

外在起因:在 v8 引擎中,GC 进行一次,此时 js 线程就会暂停,期待 GC 回收实现再持续未实现的工作,而调配给 v8 的内存越大,GC 的耗时可能就会越大,这样会升高响应的效率,例如给调配 1.5g 内存,GC 回收一个大的对象时可能会须要 1s 钟,因而才会对 v8 的内存做出了限度。

  1. 查看内存使用率:

能够通过 node 提供的 API 来查看 node 的内存:

const {rss, heapTotal, heapUsed, external} = process.memoryUsage()
{
    rss:node 常驻内存的大小,包含 v8 内存,堆外内存和 c ++ 等内存的总和;heapTotal:v8 申请的总内存;heapUsage:v8 应用的总内存;external:c++ 外面和 js 产生关联的对象内存;}
  1. 查看 GC 回收耗时

3.1 : node –trace_gc index.js:通过 –trace_gc 来查看 gc 日志信息;


(输入的信息可读性不强)

3.2 node –prof index.js
该命令能够执行完 js 文件之后,生成一系列的统计数据,输入一个 xxxx-v8.log 的日志文件;有两种路径剖析这个文件:

1)下载 tick 的 npm 包:

sudo npm install tick -g

该 npm 包能够剖析 v8.log 文件,而后输入一些列的统计数据;

node-tick-processor xxx-v8.log


图中通过 207 个 tick(零碎时钟)总占比 15.1%;

2)node 的 v5 版本后能够通过 –prof-processor 解决该文件

node --prof-process xxxx-v8.log

该指令能够具体的输入内存占比和耗时。

垃圾回收形式:

  1. 个别援用计数:

原理解释:就是一个值被赋予给其余变量,那么该值的援用次数 +1;如果含有该值的变量更新了其值,那么该值的援用次数就减一,直到整个执行周期完结后,该值的援用次数为 0,那么此时就会被垃圾回收;

let a = {a1: 1};
let b = a // 此时 a 的计次为 1
b = 0 // 此时 a 的计次为 0;

存在问题:就是存在互相援用的景象呈现,从而引发内存泄露;

// a.js
import b from 'b.js'
export let a = {c: b}

// b.js
import a from 'a.js'
export let b = {c: a}
  1. node 的 GC 形式:

node 的 v8 引擎将呈现在内存中的变量分为新生代和老生代,新生代是示意存活工夫不长的变量,老生代就是存活工夫较长或者常驻内存的变量,所以 v8 申请的内存会划分为两局部分给这两种生代的变量应用,对应的回收这两种生代的 GC 也不一样;

对于这两种生代的内存调配,v8 也是有相干的限度:
新生代的内存下限:64 位 —-> 64MB 内存占用;32 位 —> 32MB 内存占用;
老生代的内存下限:64 位 —-> 1400MB 内存占用;32 位 —> 700MB 内存占用;

对于老生代,还能够细分出几块区域:

a) old object space 即大家口中的老生代,不是全副老生代,这里的对象大部分是由新生代降职而来
b) large object space 大对象存储区域,其余区域无奈存储下的对象会被放在这里,根本是超过 1M 的对象,这种对象不会在新生代对象中调配,间接寄存到这里,当然了,这么大的数据,复制老本很高,根本就是在这里期待命运的来临不可能承受仅仅是知其然,而不知其所以然
c) Map space 这个玩意,就是存储对象的映射关系的,其实就是暗藏类;
d) code space 简略点说,就是寄存代码的中央,编译之后的代码,是依据大佬们写的代码编译进去的代码
2.1 scavenge 算法:
对于新生代,v8 会采纳空间换工夫的形式,将新生代的空间划分成一半是 From 空间,一半是 To 空间;From 空间次要是用来调配对象,并将程序在运行中新生的对象所占的内存储存在该空间,而 To 空间则是在开启垃圾回收时,对于 From 空间中还存活的变量会移植到该空间;From 空间和 To 空间不会时变化无穷,他们是动静切换,也就是说当进行玩一次垃圾回收后会将这两个空间进行对换;当然如果一个变量间断两次都存活在 To 空间,就会认为该变量须要存活很久,所以须要将其移植到另一个空间存储,这称为降职。

此外在新生代中这两个空间都简称为 semiSpace 空间。

以下就是 scavenge 算法的一次垃圾回收的过程:
首先:在程序运行阶段会将调配给新创建对象的内存空间,是从 From 空间调配;

其次当开启垃圾回收的时候,每个 From 空间中的变量都会进行判断,判断是否能够进入 To 空间,还是移植到另一个空间,判断的根据是:
1) 该变量曾经在上一次的垃圾回收中存在过 To 空间;
2) To 空间曾经占据了整个空间的 25%;
此时该变量都会进入到老生代的空间;

最初,等所有变量都复制到 To 空间后,此时就会替换空间,To 空间会变成 From 空间,From 空间会变成 To 空间,因而原来 To 空间中的变量就会持续在 From 空间中存活;

毛病:空间换工夫,缩小了存储变量的内存,因而只能用来进行小规模的垃圾回收;
长处:回收的效率高,回收一次的耗时很小,不太影响失常的响应;

留神:25% 是因为新生代只占据着这个内存的 50%,而 from 和 to 按理应该各分到 25%,如果其中一方分到超过 25%,那么在替换空间的时候,另一方就会呈现不均衡状态,此时就会将数据转移到老生代。

2.2 mark-sweep:
在老生代中,会采纳标记革除的办法进行垃圾回收,对于该空间的变量一开始会标记为 ” 存活 ” 的标记,而后当进行垃圾回收后会查看该空间是否没有标记的变量,如果没有则清理;

2.3 mark-compact:
在老生代中解决 mark-sweep 外还会存在另一种回收形式,因为前者在删除后会呈现很个不间断的 ” 坑 ”,以至于呈现很多碎片,影响内存的调配,所以须要在革除之前,将标记的变量移到一边,残余没标记的变量汇合在一边批准革除,缩小碎片内存;

在速度来说 m - s 比 m - c 要快,但碎片 mc 比 m - s 要少;

2.4 优化垃圾回收:

全进展:当开启一次垃圾回收时,以后执行的代码会被进行,期待垃圾回收实现后再持续后续的执行,对于新生代来说因为新生的变量存活的工夫不长,因而全进展的工夫不长,影响不大,然而对于老生代来说,因为 m - s 和 m - c 都是比拟耗时,所以如果齐全期待其回收完,全进展的工夫会很长,因而会影响到失常的业务运作;

增量标记:正如上述所说,对于老生代的垃圾回收所带来的全进展代价较高,须要优化,而老生代的回收是分为几个阶段,标记 - 革除 - 整顿,而个别最耗时的是处于标记,所以采纳了将标记进行拆分成多个小分片,每执行完一个小分片,就会复原一段 js 逻辑让其执行一会儿,而后再继续执行分片的回收,直到所有的分片回收实现,完后再执行执行后续的 js 逻辑。

提早革除:这个也很简略,在标记之后,引擎分明直到哪些是能够革除的对象,然而并不代表须要同时革除掉这些垃圾,所以引擎抉择按需清理,优先从须要的页面开始,逐渐清理所有的页面垃圾,而后就算就实现了一整个垃圾回收周期。

node 引发内存泄露:

  1. 闭包:

闭包自身不会引发内存泄露,因为放在闭包的中变量是须要存活在老生代的变量,是应用其所须要的变量,而不是无用的变量;而且也不会因为一两次应用闭包就会呈现内存泄露;

然而闭包如果使用不当,也会引发内存泄露,如应用的地位在全局,因而有如下几个:

1) 请勿在闭包中进行循环援用,这样会造成比较严重的内存透露。

2) 对于函数中调用的定时器,在不应用时,须要及时革除掉。

3) 尽量不要应用全局变量定义闭包的援用,因为全局变量仅会在页面刷新时被回收【除非手动革除】;

4) 为了防止闭包的内存透露,最好在函数援用的变量不被应用时,给其赋值为 null[指向空],这样内存将会被回收;

  1. 缓存:

缓存一贯是后端优化性能和响应速度的首选,然而对于 node 来说,如果采纳缓存,一不小心就会呈现内存泄露从而引发 oom 景象;因为后端的利用是一个单例再运行,不会让其进行,所以如果长年累月的缓存数据,最初会有可能超出 node 的 v8 内存下限;

const cache = {};
function get(key) {if (cache[key]) {return cache[key];
    } else {cache[key] = IO 操作;return cache[key];
    }
}

正如下面所示,要再 node 中应用缓存必须要对其进行束缚:
1)加上过期清理机制;
2)制订阀值,超过阀值删除能够删除的内容;

  1. 生产队列生产速度小于生产速度:

当生产的速度小于生产的速度,就会呈现队列过长,以至于超过下限,因而须要对这种状况,队列的长度做出限度;

  1. 定时器和事件回调函数用完后不清理;
  2. dom 节点的援用用完没有被设为 null;

node 解决内存泄露的工具:

  1. node-heapdump:

用来捕获内存的快照,会生成一个 heapdump 文件,该文件是一个 json 文件,须要利用 chrome 的 profile 引入该文件能力查看:
应用形式:

npm install heapdump

const heap = require('heapdump');
heapdump.writeSnapshot('xxxx.heapsnapshot')

或者:
该工具应用简洁,不仅提供了生成快照的办法,还能够在命令行中执行,只有引入该 npm 包就行;
kill -USR2 <pid>
来发送信号生成堆转储文件。

该文件须要利用到 chrome 查看:

介绍 1:
Summary:以构造函数名分类显示
Comparison:比拟多个快照之间的差别
Containment:查看整个 GC 门路
Statistics:以饼状图显示内存占用信息


介绍 2:
Contructor:构造函数名,如 Object、Module、Socket,(array)、(string)、(regexp) 等加了括号的别离代表内置的 Array、String、Regexp。
Distance:到 GC roots(GC 根对象)的间隔。GC 根对象在浏览器中个别是 window 对象,在 Node.js 中是 global 对象。间隔越大,阐明援用越深,则有必要重点关注一下,极大可能是内存透露的对象。
Objects count:对象个数,即开展有多少项。
Shallow Size:对象本身大小,不包含它援用的对象。
Retained Size:对象本身大小和它援用对象的大小,即该对象被 GC 之后所能回收的内存的大小。

  1. memwatch

该 npm 包能够察看 gc 的回收状况,通过提供的事件监听从而得出它经验过几次 GC,包含 scavenge,mark-sweep,increase mark 等;
应用时采纳爱彼领提供的 node-memwatch 的 npm 包:
npm i @airbnb/node-memwatch

罕用的 api:
stats 事件:如果进行一次 gc,就会触发该事件,进行多少次 gc 回收,就会触发多少次 stats 事件;

memwatch.on('stats', function(stats) {...});


(外面的工夫是采纳 ns,蕴含着 scavenge,mark-sweep-compace,increate-mark)

HeapDiff: 通过包裹一个执行体,而后会失去两个快照,并比照两个快照,得出他们的差别点;

const memwatch = require('@airbnb/node-memwatch');
const hp = new memwatch.HeapDiff()
var a = [];
for (let i = 0; i < 1000000; i++) {a.push(new Array(100));
}
const diff =  hp.end()
console.log('diff',diff)

正文完
 0