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

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)

【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年99元

阿里云限时活动-1核2G-1M带宽-40-100G ,特惠价87.12元/年(原价1234.2元/年,可以直接买3年),速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

You may also like...

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据