李彥廣趙科研
(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)。