V8的垃圾回收機(jī)制與內(nèi)存限制
一般的后端開(kāi)發(fā)語(yǔ)言中,基本的內(nèi)存使用上沒(méi)有什么限制吏饿,然而在Node中通過(guò)Javascript使用內(nèi)存時(shí)就會(huì)發(fā)現(xiàn)只能使用部分內(nèi)存(64位系統(tǒng)中約為1.4GB捏卓,32位系統(tǒng)中約為0.7GB)丛忆。這樣限制,會(huì)導(dǎo)致2GB的文件讀取內(nèi)存無(wú)法進(jìn)行字符串分析處理盖袭,即使物理內(nèi)存有32GB失暂,在Node單進(jìn)程中,計(jì)算機(jī)的內(nèi)存資源無(wú)法得到充分的使用苍凛。
背后核心的問(wèn)題在于Node基于V8構(gòu)建趣席,Node中使用的JS對(duì)象基本上都是通過(guò)V8的方式分配和管理,V8在瀏覽器足夠使用醇蝴,但是在后臺(tái)服務(wù)器開(kāi)發(fā)中卻無(wú)法滿足需求宣肚。
查看內(nèi)存使用情況
node
precess.memoryUsage()
V8的垃圾回收機(jī)制
V8的垃圾回收算法
主要是基于分代垃圾回收機(jī)制,現(xiàn)在的垃圾回收算法中按照對(duì)象的存活事件將內(nèi)存的垃圾回收進(jìn)行不同的分代悠栓,然后對(duì)不同分代的內(nèi)存施以更高效的算法霉涨。
-
V8的內(nèi)存分代
主要將內(nèi)存分為新生代和老生代兩代。新生代中的對(duì)象位存活時(shí)間較短的對(duì)象惭适,老生代中的對(duì)象位存活時(shí)間較長(zhǎng)或者常駐內(nèi)存的對(duì)象笙瑟。
內(nèi)存的設(shè)置需要在Node啟動(dòng)的時(shí)候設(shè)置,無(wú)法動(dòng)態(tài)改變
--max-new-space-size 1024 單位MB --max-old-space-size 1024 單位KB
默認(rèn)設(shè)置下癞志,在64位系統(tǒng)和32位系統(tǒng)下只能使用越1.4GB和約0.7GB的大小
-
Scavenge算法
在分代的基礎(chǔ)上往枷,新生代中的對(duì)象主要通過(guò)Scanvenge算法進(jìn)行垃圾回收,具體是一種采用復(fù)制的方式實(shí)現(xiàn)的垃圾回收算法凄杯,它將堆內(nèi)存一分為二错洁,兩個(gè)空間,只有一個(gè)處于使用中戒突,另外處于閑置狀態(tài)屯碴,處于使用狀態(tài)的空間位From空間,處于閑置狀態(tài)的空間稱(chēng)為T(mén)o空間膊存。當(dāng)分配對(duì)象時(shí)导而,首先在From空間進(jìn)行分配,當(dāng)開(kāi)始進(jìn)行垃圾回收隔崎,會(huì)檢查From空間中的存活對(duì)象今艺,存活對(duì)象將被復(fù)制到To空間,非存活對(duì)象占用的空間將會(huì)被釋放爵卒。完成復(fù)制后虚缎,F(xiàn)rom空間和To空間的角色進(jìn)行兌換。
Scavenge的缺點(diǎn)是只能使用堆內(nèi)存中的一半技潘,典型的犧牲了空間換取時(shí)間的算法遥巴,非常適合新生代對(duì)象的生命周期較短。
當(dāng)一個(gè)對(duì)象經(jīng)過(guò)多次復(fù)制依然存活享幽,它將會(huì)被認(rèn)為是生命周期較長(zhǎng)的對(duì)象铲掐,隨后會(huì)被移動(dòng)到老生代中
-
Mark-Sweep & Mark-Compact
對(duì)于老生代中的對(duì)象,由于存活對(duì)象占較大比重值桩,所以采用標(biāo)記清楚摆霉,分為標(biāo)記和清除兩個(gè)階段,在標(biāo)記階段遍歷堆中的雖有對(duì)象奔坟,并標(biāo)記活著的對(duì)象携栋,在隨后的清除階段,只清除沒(méi)有被標(biāo)記的對(duì)象咳秉。為了解決清除后出現(xiàn)的內(nèi)存碎片問(wèn)題婉支,出現(xiàn)Mark-Compact
回收算法 Mark-Sweep Mark-Compact Scavenge 速度 中等 最慢 最快 空間開(kāi)銷(xiāo) 少(碎片) 少(無(wú)碎片) 雙倍空間(無(wú)碎片) 是否移動(dòng)對(duì)象 否 是 是 ?
高效使用內(nèi)存
作用域
JS的作用域只有函數(shù)調(diào)用,以及全局作用域
-
函數(shù)調(diào)用
函數(shù)在每次被調(diào)用會(huì)創(chuàng)建對(duì)象的作用域澜建,函數(shù)執(zhí)行結(jié)束向挖,該作用域?qū)?huì)銷(xiāo)毀,同事作用域中聲明的局部變量分配在該作用域上炕舵,隨著作用域的銷(xiāo)毀而在下次垃圾回收時(shí)被釋放
-
變量的主動(dòng)釋放
如果變量私全局變量何之,由于全局作用域需要直接到進(jìn)程退出才能釋放,此時(shí)將導(dǎo)致引用的對(duì)象常駐內(nèi)存咽筋,此時(shí)如果需要釋放常駐內(nèi)存的對(duì)象溶推,可以將變量重新賦值null defined即可,接下來(lái)的老生代內(nèi)存清楚和整理的過(guò)程中奸攻,會(huì)被回收釋放蒜危。
-
閉包
閉包的使用會(huì)導(dǎo)致,一旦變量引用了這個(gè)中間函數(shù)舞箍,這個(gè)中間函數(shù)就不會(huì)釋放舰褪,同時(shí)也會(huì)使原始的作用域不會(huì)得到釋放,除非不再引用疏橄,才會(huì)逐步釋放占拍。
內(nèi)存的劃分
-
棧內(nèi)內(nèi)存
主要是通過(guò)V8的劃分獲得 主要受V8引擎的限制。
-
堆外內(nèi)存
主要是Stream Buffer之類(lèi)的需要處理二進(jìn)制的數(shù)據(jù) 在堆外劃分捎迫,主要是受操作系統(tǒng)的進(jìn)程常駐內(nèi)存晃酒。
大內(nèi)存應(yīng)用
Node主要提供了stream模塊用于處理大文件
stream模塊是Node的原生模塊, stream繼承EventEmitter窄绒,對(duì)于大文件我們無(wú)法通過(guò)fs.readFile()和fs.writeFile()直接進(jìn)行大文件的操作贝次,可以通過(guò)改用流的方式對(duì)大文件的操作
var reader = fs.createReadStream('in.txt')
var writer = fs.createWriteStream('out.txt')
reader.pipe(writer);