李彦广赵科研
(1 商洛学院数学与计算机应用学院,陕西 商洛 726000)(2 商洛学院经济与管理学院,陕西 商洛 726000)
浅析Node.js中的内存控制机制
李彦广1赵科研2
(1 商洛学院数学与计算机应用学院,陕西 商洛 726000)
(2 商洛学院经济与管理学院,陕西 商洛 726000)
在V8平台上构建Node.js,用户在使用过程中要通过JavaScricot进行调用的情况下,对Node.js在内存的管理进行分析介绍,在V8平台上的对内存的分配和回收机制进行了分析,提出了在使用单个Node.js进程的下,如何合理高效使用计算机的内存资源。经过测试仿真,方法是高效的。
Node.js V8 JavaScricot内存分配 回收策略
在Node.js中通过JavaScricpt使用内存通常会受到限制(64位系统下约为1.4 GB,32位系统下约为0.7 GB)[1]。这将导致Node.js无法直接操作大内存对象,例如即使物理内存有32 GB,也无法将一个2 GB的文件读入内存中进行字符串分析处理。这样在单个Node.js进程的情况下,计算机的内存资源无法得到充分利用。尽管服务器端操作大内存也不是常见场景,但有了限制后,人们的行为就如同带着镣铐跳舞,如果在实际的应用中不小心触碰到这个界限,会造成程序退出[2]。要知晓V8为何限制了内存的用量,则需要回归到V8在内存使用上的策略。知晓其原理后,才能避免问题,并更好地进行内容管理。
在V8中,所有JavaScript对象都是通过堆来进行分配的。Node.js提供了V8中内存使用量的查看方式,执行下面的代码,将得到输出的内存信息:
上述代码中,在memoryUsage()方法返回的3个属性,heapTotal和heapUsed是V8的堆内存使用情况,前者是以申请到的堆内存,后者是当前使用的量,V8的堆示意图如图1所示。
图1 V8的堆示意图
当在代码中声明变量并赋值时,所使用对象的内存就分配在堆中。如果已申请的堆空闲内存不够分配新的对象,将继续申请对内存,直到堆的大小超过V8的限制为止[3]。
至于V8为何要限制堆的大小,表层原因是V8最初为浏览器而设计,不太可能用到大量内存的场景,深层原因是V8的垃圾回收机制的限制,按官方的说法,以1.5 GB的堆内存为例,V8做一次小的垃圾回收需要50 ms以上,做一次非增量式的垃圾回收甚至需要一秒以上。这是垃圾回收中引起JavaScript线程暂停执行的时间,在这样的时间花销下,应用的性能和相应能力会直线下降。这样的情况不仅后端服务无法接受,前端浏览器也无法接受。因此,在当时的考虑下,直接限制内存是一个合理的解释。
当然,这个限制也不是不能打开,V8依然提供了选项让人们使用更多的内存。Node.js在启动时可以传递--max-old-space-size或--max-new-space-size来调整内存限制的大小,示例如下:
node--max-old-space-size=1700 test.js
node--max-new-space-size=1024 test.js
上述参数在V8初始化时生效并且不能再动态改变。如果遇到Node.js无法分配足够内存给JavaScript对象的情况,可以用此办法来放宽V8默认的内存限制,避免在执行过程中多用了一些内存就轻易崩溃[4]。
V8的垃圾回收策略主要基于分代式垃圾回收机制。在自动垃圾回收的演变过程中,人们发现没有一种垃圾回收算法能胜任所有的场景。因为在实际应用中,对象的生存周期长短不一,不同算法只能针对特殊情况具有最好的效果。为此统计学在垃圾回收算法的发展中产生了较大的作用,现代垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同的分代的内存施以更高效的算法[5]。
3.1 V8的内存分代
在V8中,主要分为新生代和老生代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象,V8的分代示意图如图2所示。
图2 V8的分代示意图
V8的整体大小就是新生代所用内存空间加上老生代的内存空间,前面提及的--mac-old-space-size命令行参数可用于设置老生代内存空间的最大值,--max-new-space-size命令行参数则用于设置新生代内存空间的大小。比较遗憾的是,这2个最大值需要在启动时指定。这意味着V8使用的内存无法依据使用情况自动扩充[6]。当内存分配过程中超过极限值时,就会引起出错。
前面提到,在默认值下如果一直分配内存,在64位系统和32位系统下会分别只能使用约1.4 GB和0.7 GB。这个限制可从V8的源码中找到。在下面的代码中,Page::kPageSize的值为1 MB。可以看到,老生代的设置在32位系统下为1 400 MB,在32位系统下为700 MB:
对于新生代,它由2个reserved-semispace-size-构成,后面将描述其原因。按机器位数不同,reserved-semispace-size-在64位系统和32位系统上分别为16 MB和8 MB,所以新生代内存的最大值在64位系统和32位系统上分别为32 MB和16 MB。
V8堆内存的最大保留空间公式为4⋆reserved-semispacesize-+max-old-space-size-。因此,默认情况下,V8堆内存的最大值在64位系统上为146 MB,32位系统上则为732 MB。这个数值可解释为何在64位系统下只能使用约1.4 GB内存和在32位系统下只能使用0.7 GB。
3.2 Scavenge算法
在分代的基础上,新生代的对象主要通过Scavenge算法进行垃圾回收。在Scavenge的具体实现中,主要采用了Cheney算法,该算法由C.J.Cheney于1970年首次发表在ACM论文上。
Cheney算法是一种采用复制的方式实现的垃圾回收算法,它将堆内存一分为二,每一部分空间称为semispace。在这2 个semispace空间中,只有一个处于使用中,另一个处于闲置。处于使用状态的semispace空间称为From空间,处于闲置的空间称为To空间。在分配对象时,先是针对From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间的角色发生对换。简而言之,在垃圾回收的过程中,就是通过将存活对象在2个semispace空间之间进行复制。Scavenge的缺点是只能使用堆内存中的一半,这是由划分空间和复制机制所决定的。但Scavenge由于只复制存活对象,并且对于生命周期短的场景存活对象只占少部分,所以它在时间效率上有优异的表现。
由于Scavenge是典型的牺牲空间换取时间的算法,所以无法大规模地应用到所有的垃圾回收中。但可以发现,Scavenge非常适合应用在新生代中,因为新生代中对象的生命周期较短,恰恰是和这个算法,故而V8的对内存示意图应当如图3所示。
图3 V8的堆内存示意图
实际使用的对内存是新生代中的2个semispace空间大小和老生代所用内存大小之和。当一个对象经过多次复制依然存活时,它将被认为是生命周期较长的对象。这种较长生命周期对象随后会被移动到老生代中,采用新的算法进行管理[6]。对象从新生代移动到老生代中的过程称为晋升。
在单纯的Scavenge过程中,From空间中的存活对象会复制到To中,然后对From空间和To空间进行角色对换(又称翻转)。但在分代式垃圾回收的前提下,From空间中的存活对象在复制前需要进行检查。在一定条件下,需将存活周期长的对象移动到老生代中,也就是完成对象晋升。对象晋升的条件主要有2个:一个是对象是否经历过Scavenge回收,一个是To空间的内存占用比超过限制。
3.3 查看垃圾回收日志
查看垃圾回收日志的方式主要是在启动时添加--trace-gc参数,在进行垃圾回收时将会从标准输出中打印垃圾回收的日志信息,执行结束后,将会在gc-log文件中得到所有垃圾回收信息。通过分析垃圾回收日志,可以了解垃圾回收的运行状况,找出垃圾回收的哪些阶段比较耗时,触发的根本原因是什么。
从V8的自动垃圾回收机制的实际角度可以看到,V8对内存使用进行限制的缘由,新生代设计为一个较小的内存空间是合理的,而老生代空间过大对于垃圾回收并无特别意义。V8对内存限制的设置对于Chrome浏览器这种每个选项卡页面使用一个V8实例而言,内存的使用是绰绰有余了。对Node.js编写的服务器端来说,限制也并不影响正常场景下的服务。但对于V8的垃圾回收特点和JavaScricot在单线程上的执行情况,垃圾回收是影响性能的因素之一。想要注意垃圾回收尽量少地进行,尤其是全堆垃圾回收。以Web服务器中的会话实现为例,一般是通过内存来存储,但在访问量大的时候会导致老生代中的存活对象骤增,不仅造成清理和整理过程费时,还会造成内存紧张,甚至溢出。
[1]朴 灵.深入浅出Node.js[M].北京:人民邮电出版社,2013,12:22-32.
[2]李 梅.浅谈Node.js异步编程中回调和异步调用的区别[J].通信世界,2015(6):203.
[3]李彦广.基于Spark+MLlib分布式学习算法的研究[J].商洛学院学报,2015,29(2):16-19.
[4]万里晴,杨 浩.探究基于V8引擎的Node.js在各应用领域的发展[J].通信世界,2015(7):97.
[5]朴 灵.深入浅出Node.js[M].北京:人民邮电出版社,2013,12:45-55.
[6]李彦广.基于服务特性的CDN带宽动态分配策略[J].西安工业大学学报,2015(5):365-368.
A Brief Analysis of Memory Control Mechanisms for Node.js
LI Yan-guang1,ZHAO Ke-yan2
(1.College of Mathematics and Computer Application,Shangluo University,Shangluo Shaanxi 726000,China)
(2.Faculty of Economics and Management,Shangluo University,Shangluo Shaanxi 726000,China)
Users make a call by JavaScricot in building the Node.js in V8 platform.This paper analyzes and introduces the application of Node.js in memory management.The memory distribution and recovery mechanism are analyzed in V8 platform.This paper puts forward the method of using memory resource of computer reasonably and effectively in a single Node.js.The test and simulation results show that this method has high availability.
Node.js;V8;JavScricot;memory allocation;recovery strategy
TP393.4
A
1008-1739(2015)24-63-3
定稿日期:2015-11-26
陕西省教育厅科研专项:商洛市农村中小学信息技术教师专业素养评价及专业发展途径研究(2013jk1160);陕西省教育厅科研专项:天梯(2286)。