当前位置: 首页 > 后端技术 > Node.js

JavaScript内存管理与V8优化

时间:2023-04-03 15:16:42 Node.js

JavaScript内存管理JavaScript有一个自动垃圾收集机制(GarbageCollection)简称GC。垃圾回收机制会中断整个代码执行,释放不能再使用的变量,释放内存。这种工作机制是周期性的,我们将在下面详细讨论。可释放对象functionfn1(){varobj1={name:'xiaomuchen',age:'20'}}functionfn2(){varobj2={name:'xiaomuchen',age:'20'}returnobj2}vara=fn1()varb=fn2()console.log(a,b)//undefined,{name:"xiaomuchen",age:"20"}我们对比一下上面两个函数,fn1在函数中声明了变量obj1并赋值,这个变量在函数执行后就不能再访问了,fn2把函数中的变量obj2返回给最后的全局变量b,所以{name:'xiaomuchen',age:'20'}this对象(或obj2)仍然可以访问。JavaScript回收机制通过判断变量是否可以访问来决定回收哪些变量。标记清除和引用计数那么JavaScript是如何判断一个变量是否可访问的呢?这给我们带来了标记清理和引用计数。标记清除:标记清除是目前大多数JavaScript引擎使用的判断方式,通过标记变量的状态来判断是否可以被回收。当在环境中声明变量时,标记进入环境。从理论上讲,进入环境的变量永远不应该被释放,因为它可以在环境中的任何地方随时访问。当环境被破坏时(比如函数被执行),变量被标记为离开环境,等待回收。functionfn(){vara={count:10}//被标记,进入环境varb={count:20}//被标记,进入环境}fn();//执行后,b被标记并离开环境引用计数:JavaScript引擎维护一个引用表,用于保存所有资源在内存中的引用计数。当资源被引用一次时,它将被引用+1。当资源被取消引用或退出变量的函数范围时,它将被引用为-1。当资源的引用计数为0时,表示该值不可访问,等待回收。(注:引用计数从1到0的过程可以不执行,直接标记为可回收,不做加减运算,节省开销)functionfn(){vara={count:10}//resource{count:10}引用次数为1a={count:20}//resource{count:20}被引用1,resource{count:10}被引用0,等待回收//dosomeThing}fn();//资源{count:20}被释放,但是引用计数中存在循环引用,它不会被回收。这个例子来自《JavaScript 高级程序设计》,但是我想了很久。如果引用计数把a.param算作一个变量,那么就不会有这个问题。引用计数的不同实现方式会产生不同的结果。functionfn(){vara={count:10}varb={count:20}a.param=b//b被引用2b.param=a//a被引用2}fn();//a和b的引用数是1GC缺陷,分代收集和增量GC和其他语言一样,GC会中断代码执行,停止其他操作。因为需要遍历所有对象,回收所有无法访问的对象,这个操作可能需要100ms以上。在新版本的V8引擎中,引入了两种优化方式:1.GenerationGC,2.IncrementGC(增量GC)分代收集:目的是通过使用频率和使用次数来区分新生代和新生代对象的持续时间。老一代对象。多回收新生代区(younggeneration),少回收老年代区(tenuredgeneration),减少每次需要遍历的对象数量,从而减少每次GC增量GC的耗时:需要较长时间的遍历和回收操作拆分操作减少了中断时间,但增加了上下文切换开销。Node.js中的GC性能当我们使用Node.js构建稳定的服务时,我们需要考虑服务器内存开销。以下Node.js内存回收执行示例:执行代码node--trace_gc--trace_gc_verbosetest.js跟踪网络服务的GC。[41204:0x102001c00]内存缩减器:调用率0.056,低分配,前景[41204:0x102001c00]内存缩减器:启动GC#1[41204:0x102001c00]堆增长因子1.1基于mu=0.970,speed_ratio=42956(gc=675253),mutator=16)[41204:0x102001c00]增长:旧大小:21382KB,新限制:33604KB(1.1)[41204:0x102001c00]内存缩减器:完成GC#1(将做更多)[41204:0x102001c00]156410ms:Mark-sweep27.7(50.0)->21.0(30.0)MB,12.4/0.0ms(+20.4msin7stepssincethestartofmarking,maximumstep4.8ms)[增量标记任务:完成增量标记][请求旧空间中的GC]。[41204:0x102001c00]内存分配器,已用:30756KB,可用:1435612KB[41204:0x102001c00]新空间,已用:169KB,可用:838KB,已提交:1024KB[41204:0x102001c00]旧空间,已用:16662KB,可用:2417KB,已提交:19412KB[41204:0x102001c00]代码空间,已用:4078KB,可用:178KB,已提交:5120KB[41204:0x102001c00]映射空间,已用:642KB,可用:0KB,已提交:2128KB[41204:0x102001c00]大对象空间,已用:0KB,可用:1434571[41204:0x102001c00]全部空间,已用:21552KB,可用:1438005KB,已提交:27684KB[41204:0x102001c00]外部内存报告:1026KB[41204:0x102001c00]GC花费的总时间:158.6ms[41204:0x102001c00]内存减少器:调用率0.003,lowalloc,foreground首先我们可以看到Node.js区分Newspace,Oldspace等来划分搜索空间并提示(从标记开始7步+20.4ms,最大步长4.8ms)告诉我们这个标记步骤分7步进行,最长的一步需要4.8ms。这使得JavaScript能够很好地支持高实时应用的开发,原文。总结由于篇幅有限,留几个小问题供大家思考:闭包一定会导致内存不可回收?如何监控Node.js服务的内存开销,以及如何处理不可预测的内存泄漏?作者:肖木辰,github。