node的内存限制:内存限制及原因:对于node来说,内存一直是限制node在后端大量使用的原因,因为node的v8引擎可以从服务器的内核中分配不多的内存:对于32位系统,v8分配的内存一般只有0.7g;对于64位系统,v8分配的内存一般只有1.4g;如果内存容量超过对应系统v8的上限,就会出现内存不足,从而退出节点进程(单进程)。表面原因:因为v8不仅需要在服务器环境下使用,还需要在浏览器环境下使用,所以为了平衡两者的需求,我们只能得到上述的v8内存限制;内部原因:在v8引擎中,GC执行一次,此时js线程会暂停,等待GC回收完成再继续未完成的工作,分配给v8的内存越大,GC时间越长可能会,这样会降低响应的效率,比如分配1.5g的内存,GC回收一个大对象可能需要1s,所以v8的内存是有限的。查看内存使用情况:可以通过node提供的API查看node的内存:const{rss,heapTotal,heapUsed,external}=process.memoryUsage(){rss:node常驻内存大小,包括v8内存和off-heapmemory内存和c++的总和;heapTotal:v8请求的总内存;heapUsage:v8使用的总内存;external:c++中js关联的对象内存;}查看GC恢复时间3.1:node--trace_gcindex.js:通过--trace_gc查看gc日志信息;(输出的信息可读性不是很好)3.2node--profindex.js该命令执行js文件后可以生成一系列的统计数据,并输出一个xxxx-v8.log日志文件;分析这个文件有两种方法:1)下载tick的npm包:sudonpminstalltick-gnpm包可以分析v8.log文件,然后输出一系列的统计数据;node-tick-Processorxxx-v8.log图中,207个ticks(系统时钟)一共通过了15.1%;2)nodev5版本之后,可以通过--prof-processornode--prof-processxxxx-v8.log这个命令来处理文件,这个命令可以详细的输出内存占比和耗时。垃圾回收方式:一般引用计数:原理解释:当一个值被赋值给其他变量时,该值的引用次数增加1;如果包含该值的变量更新其值,则对该值的引用数减一。直到整个执行周期结束,该值的引用计数为0,则此时会被垃圾回收;让a={a1:1};letb=a//此时a的计数为1b=0//此时a的计数为0;存在问题:存在相互引用现象,导致内存泄漏;//a.jsimportbfrom'b.js'exportleta={c:b}//b.jsimportafrom'a.js'exportletb={c:a}节点的GC方法:节点的v8引擎划分出现在内存中的变量分为新生代和老年代。新生代意味着生存时间不长老年代是生存时间长或者驻留在内存中的变量,所以v8申请的内存会针对这两类生成的变量分为两部分,以及这两类发电的相应回收。相同;对于这两个新生代的内存分配,v8也有相关的限制:新生代的内存上限:64位---->64MB内存占用;32位--->32MB内存占用;老年代内存上限:64位---->1400MB内存使用;32位--->700MB内存使用;对于老年代,也可以细分为几个区域:a)老对象空间就是大家口中的老年代,并不是所有的老年代,这里的大部分对象都是从新生代提升过来的b)大对象空间大对象存储区,其他区域不能存储的对象会放在这里,基本超过1M的对象,这样的对象不会在新生代对象中Allocate,直接存储在这里。当然,如此庞大的数据量,复制的成本很高。基本上就是等待缘分降临。不可能接受它只是知道它是什么,而不是为什么。c)Mapspace这个东西就是存放对象的映射关系,其实就是一个隐藏类;d)代码空间简单来说就是存放代码的地方。编译后的代码是按照大佬们写的代码编译的2.1清道夫算法:对于新生代,v8会采用空间换时间的方式,将新生代的空间分成一半从空间,一半进入到空间;占用的内存存放在这个空间,而打开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.2mark-sweep:在老年代,垃圾回收会采用mark-clearing的方式。这个空间里的变量一开始会被标记为“存活”,然后在垃圾回收之后,会检查这个空间是否没有被标记,如果没有变量,就清理掉;2.3mark-compact:oldgeneration除了mark-sweep还有另外一种回收方式,因为前者删除后会有很多不连续的“坑”,这样会有很多Fragmentation影响内存的分配,所以是需要在清除前将标记的变量移到一边,其余未标记的变量同意清除,以减少内存碎片;从速度上来说,m-s比m-c快,但是碎片化的mc比m-c快m-s少;2.4优化垃圾回收:全暂停:开启垃圾回收后,会停止当前正在执行的代码,待垃圾回收完成后继续执行后续代码。对于新生代,由于新变量的生存时间不长,所以全停顿时间不长,影响不大,但是对于老年代,因为m-s和m-c是耗时的,所以如果你等待他们完全回收,完全停顿的时间会很长,所以会影响正常的业务操作;增量标记:上面说到老年代垃圾回收造成的fullpause成本高,需要优化,而老年代的回收分为几个阶段,mark-clear-sorting,一般是最耗时的是标记,所以标记被拆分成多个小片段,每执行一个小片段,就会恢复一段js逻辑执行一段时间,然后继续执行片段的回收,直到回收所有的片段,完成后再执行后续的js逻辑。延迟清洗:这个也很简单。标记之后,引擎知道到现在可以清理哪些对象,但并不代表这些垃圾需要同时清理,所以引擎选择按需清理,先从需要的页面开始,逐步清理所有页面垃圾,完成一个完整的垃圾回收周期。node导致内存泄漏:闭包:闭包本身不会导致内存泄漏,因为放在闭包中的变量是需要在老年代存活的变量,使用自己需要的变量,而不是无用的变量;并且不会因为闭包被使用一次或两次而导致内存泄漏;但是,如果闭包使用不当,也会造成内存泄漏,比如使用的位置是全局的,所以有以下几点:1)不要在闭包中进行操作循环引用会导致严重的内存泄漏。2)关于函数中调用的定时器,不用的时候需要及时清除。3)尽量不要使用全局变量来定义闭包引用,因为全局变量只有在页面刷新时才会被回收[除非手动清除];4)为了避免闭包内存泄漏,最好不要在使用函数引用的变量时,给它赋值null[指向空],这样内存会被回收;cache:缓存一直是后端优化性能和响应速度的首选,但是对于node来说,如果使用缓存,一不小心就会出现内存泄露导致OOM现象;因为后端应用是单实例运行,不会停止,所以如果数据缓存多年,最终可能会超过node的v8内存限制;常量缓存={};functionget(key){if(cache[key]){返回缓存[key];}else{缓存[key]=IO操作;返回缓存[键];}}如上所示,在node中使用缓存必须对其进行约束:1)添加过期清理机制;2)设置一个阈值,删除超过阈值可以删除的内容;消费队列的消费速度低于生产速度:当消费速度低于生产速度时,会出现队列超长,以至于超过上限,所以需要限制队列的长度在这种情况下排队;定时器和事件回调函数用完后不清理;dom节点的引用在用完时不设置为null;node解决内存泄露问题工具:node-heapdump:用于抓取内存快照,会生成一个heapdump文件,是一个json文件,需要使用chromeprofile导入这个文件查看:用法:npminstallheapdumpconstheap=require('heapdump');heapdump.writeSnapshot('xxxx.heapsnapshot')或者:这个工具使用简单,不仅提供了生成快照的方法,还可以在命令行执行,只要npm包已导入;kill-USR2
