自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入探究Node | (4)“內(nèi)存控制” 有十五問

開發(fā) 前端
在V8中,所有的JavaScript對(duì)象都是通過堆來進(jìn)行分配的。Node提供了V8中內(nèi)存使用量的查看方式,執(zhí)行下面的代碼,將得到輸出的內(nèi)存信息。

本文轉(zhuǎn)載自微信公眾號(hào)「前端陽光」,作者事業(yè)有成的張啦啦 。轉(zhuǎn)載本文請(qǐng)聯(lián)系前端陽光公眾號(hào)。

 1. V8是用什么給對(duì)象分配內(nèi)存的呢?

2. V8為何要限制堆的大小?

3. 原來如此,那你知道垃圾回收機(jī)制的策略是什么嗎?

4. 為什么要分代呢?

5. 哦,那你談?wù)勈窃趺捶执?

6. 那 新生代是怎么回收的?

7. 很好奇,一個(gè)新生代它是怎么晉升成老生代的。

8. 為什么要設(shè)置25%這個(gè)這么低的值呢?

9. 新生代的對(duì)象晉升后就成老生代了,那老生代為什么不能用Scavenge回收?

10. 那老生代的對(duì)象該怎么處理?

11. 那為什么還要標(biāo)記整理?

12. 咦!既然標(biāo)記整理是基于標(biāo)記清除上演變而來的,也就是它包括了標(biāo)記清除,這么棒,那就用標(biāo)記整理好了,干嘛還要說它結(jié)合標(biāo)記清除使用呢?

13. 原來是這樣啊,要是垃圾回收算法時(shí)間花費(fèi)很長,豈不是就要卡頓?

14. 你知道Buffer對(duì)象嗎?Buffer對(duì)象是通過V8分配內(nèi)存的嗎?

15. 可以利用fs.readFile()和fs.writeFile()方法 來 讀寫大文件嗎?

1. V8是用什么給對(duì)象分配內(nèi)存的呢?

在V8中,所有的JavaScript對(duì)象都是通過堆來進(jìn)行分配的。Node提供了V8中內(nèi)存使用量的查看方式,執(zhí)行下面的代碼,將得到輸出的內(nèi)存信息:

  1. $ node 
  2. > process.memoryUsage(); 
  3. { rss: 14958592, 
  4.   heapTotal: 7195904, 
  5.   heapUsed: 2821496 } 

在上述代碼中,在memoryUsage()方法返回的3個(gè)屬性中,heapTotal和heapUsed是V8的堆內(nèi)存使用情況,前者是已申請(qǐng)到的堆內(nèi)存,后者是當(dāng)前使用的量。至于rss為何,我們在后續(xù)的內(nèi)容中會(huì)介紹到。V8的堆示意圖:

當(dāng)我們在代碼中聲明變量并賦值時(shí),所使用對(duì)象的內(nèi)存就分配在堆中。如果已申請(qǐng)的堆空閑內(nèi)存不夠分配新的對(duì)象,將繼續(xù)申請(qǐng)堆內(nèi)存,直到堆的大小超過V8的限制為止。

2. V8為何要限制堆的大小?

表層原因?yàn)閂8最初為瀏覽器而設(shè)計(jì),不太可能遇到用大量內(nèi)存的場景。對(duì)于網(wǎng)頁來說,V8的限制值已經(jīng)綽綽有余。

深層原因是V8的垃圾回收機(jī)制的限制。按官方的說法,以1.5 GB的垃圾回收堆內(nèi)存為例,V8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。這是垃圾回收中引起JavaScript線程暫停執(zhí)行的時(shí)間,在這樣的時(shí)間花銷下,應(yīng)用的性能和響應(yīng)能力都會(huì)直線下降。這樣的情況不僅僅后端服務(wù)無法接受,前端瀏覽器也無法接受。因此,在當(dāng)時(shí)的考慮下直接限制堆內(nèi)存是一個(gè)好的選擇。

3. 原來如此,那你知道垃圾回收機(jī)制的策略是什么嗎?

V8的垃圾回收策略主要基于分代式垃圾回收機(jī)制。

4. 為什么要分代呢?

因?yàn)樵趯?shí)際的應(yīng)用中,對(duì)象的生存周期長短不一,不同的算法只能針對(duì)特定情況具有最好的效果。為此,現(xiàn)代的垃圾回收算法中按對(duì)象的存活時(shí)間將內(nèi)存的垃圾回收進(jìn)行不同的分代,然后分別對(duì)不同分代的內(nèi)存施以更高效的算法。

5. 哦,那你談?wù)勈窃趺捶执?

在V8中,主要將內(nèi)存分為新生代和老生代兩代。新生代中的對(duì)象為存活時(shí)間較短的對(duì)象,老生代中的對(duì)象為存活時(shí)間較長或常駐內(nèi)存的對(duì)象。圖為V8的分代示意圖。

6. 那新生代是怎么回收的?

在分代的基礎(chǔ)上,新生代中的對(duì)象主要通過Scavenge算法進(jìn)行垃圾回收。是一種采用復(fù)制的方式實(shí)現(xiàn)的垃圾回收算法。

它將堆內(nèi)存一分為二,每一部分空間稱為semispace。在這兩個(gè)semispace空間中,只有一個(gè)處于使用中,另一個(gè)處于閑置狀態(tài)。處于使用狀態(tài)的semispace空間稱為From空間,處于閑置狀態(tài)的空間稱為To空間。當(dāng)我們分配對(duì)象時(shí),先是在From空間中進(jìn)行分配。當(dāng)開始進(jìn)行垃圾回收時(shí),會(huì)檢查From空間中的存活對(duì)象,這些存活對(duì)象將被復(fù)制到To空間中,而非存活對(duì)象占用的空間將會(huì)被釋放。完成復(fù)制后,F(xiàn)rom空間和To空間的角色發(fā)生對(duì)換。

簡而言之,在垃圾回收的過程中,就是通過將存活對(duì)象在兩個(gè)semispace空間之間進(jìn)行復(fù)制。

Scavenge的缺點(diǎn)是只能使用堆內(nèi)存中的一半,這是由劃分空間和復(fù)制機(jī)制所決定的。但Scavenge由于只復(fù)制存活的對(duì)象,并且對(duì)于生命周期短的場景存活對(duì)象只占少部分,所以它在時(shí)間效率上有優(yōu)異的表現(xiàn)。

由于Scavenge是典型的犧牲空間換取時(shí)間的算法,所以無法大規(guī)模地應(yīng)用到所有的垃圾回收中。但可以發(fā)現(xiàn),Scavenge非常適合應(yīng)用在新生代中,因?yàn)樾律袑?duì)象的生命周期較短,恰恰適合這個(gè)算法。

是故,V8的堆內(nèi)存示意圖應(yīng)當(dāng)如圖所示。

當(dāng)一個(gè)對(duì)象經(jīng)過多次復(fù)制依然存活時(shí),它將會(huì)被認(rèn)為是生命周期較長的對(duì)象。這種較長生命周期的對(duì)象隨后會(huì)被移動(dòng)到老生代中,采用新的算法進(jìn)行管理。對(duì)象從新生代中移動(dòng)到老生代中的過程稱為晉升。

7. 很好奇,一個(gè)新生代它是怎么晉升成老生代的。

對(duì)象晉升的條件主要有兩個(gè),一個(gè)是對(duì)象是否經(jīng)歷過Scavenge回收,一個(gè)是To空間的內(nèi)存占用比超過限制。

在默認(rèn)情況下,V8的對(duì)象分配主要集中在From空間中。對(duì)象從From空間中復(fù)制到To空間時(shí),會(huì)檢查它的內(nèi)存地址來判斷這個(gè)對(duì)象是否已經(jīng)經(jīng)歷過一次Scavenge回收。如果已經(jīng)經(jīng)歷過了,會(huì)將該對(duì)象從From空間復(fù)制到老生代空間中,如果沒有,則復(fù)制到To空間中。這個(gè)晉升流程如圖所示。

另一個(gè)判斷條件是To空間的內(nèi)存占用比。當(dāng)要從From空間復(fù)制一個(gè)對(duì)象到To空間時(shí),如果To空間已經(jīng)使用了超過25%,則這個(gè)對(duì)象直接晉升到老生代空間中,這個(gè)晉升的判斷示意圖如圖所示。

8. 為什么要設(shè)置25%這個(gè)這么低的值呢?

設(shè)置25%這個(gè)限制值的原因是當(dāng)這次Scavenge回收完成后,這個(gè)To空間將變成From空間,接下來的內(nèi)存分配將在這個(gè)空間中進(jìn)行。如果占比過高,會(huì)影響后續(xù)的內(nèi)存分配。

9. 新生代的對(duì)象晉升后就成老生代了,那老生代為什么不能用Scavenge回收?

對(duì)于老生代中的對(duì)象,由于存活對(duì)象占較大比重,再采用Scavenge的方式會(huì)有兩個(gè)問題:一個(gè)是存活對(duì)象較多,復(fù)制存活對(duì)象的效率將會(huì)很低;另一個(gè)問題依然是浪費(fèi)一半空間的問題。這兩個(gè)問題導(dǎo)致應(yīng)對(duì)生命周期較長的對(duì)象時(shí)Scavenge會(huì)顯得捉襟見肘。

10. 那老生代的對(duì)象該怎么處理?

V8在老生代中主要采用了Mark-Sweep和Mark-Compact相結(jié)合的方式進(jìn)行垃圾回收。

Mark-Sweep是標(biāo)記清除的意思,它分為標(biāo)記和清除兩個(gè)階段。與Scavenge相比,Mark-Sweep并不將內(nèi)存空間劃分為兩半,所以不存在浪費(fèi)一半空間的行為。與Scavenge復(fù)制活著的對(duì)象不同,Mark-Sweep在標(biāo)記階段遍歷堆中的所有對(duì)象,并標(biāo)記活著的對(duì)象,在隨后的清除階段中,只清除沒有被標(biāo)記的對(duì)象??梢钥闯?,Scavenge中只復(fù)制活著的對(duì)象,而Mark-Sweep只清理死亡對(duì)象?;顚?duì)象在新生代中只占較小部分,死對(duì)象在老生代中只占較小部分,這是兩種回收方式能高效處理的原因。圖為Mark-Sweep在老生代空間中標(biāo)記后的示意圖,黑色部分標(biāo)記為死亡的對(duì)象。

11. 那為什么還要標(biāo)記整理?

Mark-Sweep最大的問題是在進(jìn)行一次標(biāo)記清除回收后,內(nèi)存空間會(huì)出現(xiàn)不連續(xù)的狀態(tài)。這種內(nèi)存碎片會(huì)對(duì)后續(xù)的內(nèi)存分配造成問題,因?yàn)楹芸赡艹霈F(xiàn)需要分配一個(gè)大對(duì)象的情況,這時(shí)所有的碎片空間都無法完成此次分配,就會(huì)提前觸發(fā)垃圾回收,而這次回收是不必要的。

為了解決Mark-Sweep的內(nèi)存碎片問題,Mark-Compact被提出來。Mark-Compact是標(biāo)記整理的意思,是在Mark-Sweep的基礎(chǔ)上演變而來的。它們的差別在于對(duì)象在標(biāo)記為死亡后,在整理的過程中,將活著的對(duì)象往一端移動(dòng),移動(dòng)完成后,直接清理掉邊界外的內(nèi)存。圖為Mark-Compact完成標(biāo)記并移動(dòng)存活對(duì)象后的示意圖,白色格子為存活對(duì)象,深色格子為死亡對(duì)象,淺色格子為存活對(duì)象移動(dòng)后留下的空洞。

完成移動(dòng)后,就可以直接清除最右邊的存活對(duì)象后面的內(nèi)存區(qū)域完成回收。

12. 咦!既然標(biāo)記整理是基于標(biāo)記清除上演變而來的,也就是它包括了標(biāo)記清除,這么棒,那就用標(biāo)記整理好了,干嘛還要說它結(jié)合標(biāo)記清除使用呢?

這里將Mark-Sweep和Mark-Compact結(jié)合著介紹不僅僅是因?yàn)閮煞N策略是遞進(jìn)關(guān)系,在V8的回收策略中兩者是結(jié)合使用的。表是目前介紹到的3種主要垃圾回收算法的簡單對(duì)比。

從表中可以看到,在Mark-Sweep和Mark-Compact之間,由于Mark-Compact需要移動(dòng)對(duì)象,所以它的執(zhí)行速度不可能很快,所以在取舍上,V8主要使用Mark-Sweep,在空間不足以對(duì)從新生代中晉升過來的對(duì)象進(jìn)行分配時(shí)才使用Mark-Compact。

13. 原來是這樣啊,要是垃圾回收算法時(shí)間花費(fèi)很長,豈不是就要卡頓?

垃圾回收的3種基本算法都需要將應(yīng)用邏輯暫停下來,待執(zhí)行完垃圾回收后再恢復(fù)執(zhí)行應(yīng)用邏輯,這種行為被稱為“全停頓”(stop-the-world)。在V8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默認(rèn)配置得較小,且其中存活對(duì)象通常較少,所以即便它是全停頓的影響也不大。

但V8的老生代通常配置得較大,且存活對(duì)象較多,全堆垃圾回收(full垃圾回收)的標(biāo)記、清理、整理等動(dòng)作造成的停頓就會(huì)比較可怕,需要設(shè)法改善。

為了降低全堆垃圾回收帶來的停頓時(shí)間,V8先從標(biāo)記階段入手,將原本要一口氣停頓完成的動(dòng)作改為增量標(biāo)記(incremental marking),也就是拆分為許多小“步進(jìn)”,每做完一“步進(jìn)”就讓JavaScript應(yīng)用邏輯執(zhí)行一小會(huì)兒,垃圾回收與應(yīng)用邏輯交替執(zhí)行直到標(biāo)記階段完成。圖為增量標(biāo)記示意圖。

V8在經(jīng)過增量標(biāo)記的改進(jìn)后,垃圾回收的最大停頓時(shí)間可以減少到原本的1/6左右。V8后續(xù)還引入了延遲清理(lazy sweeping)與增量式整理(incrementalcompaction),讓清理與整理動(dòng)作也變成增量式的。同時(shí)還計(jì)劃引入并行標(biāo)記與并行清理,進(jìn)一步利用多核性能降低每次停頓的時(shí)間。

14. 你知道Buffer對(duì)象嗎?Buffer對(duì)象是通過V8分配內(nèi)存的嗎?

知道。他不是。

為何Buffer對(duì)象并非通過V8分配?這在于Node并不同于瀏覽器的應(yīng)用場景。在瀏覽器中,JavaScript直接處理字符串即可滿足絕大多數(shù)的業(yè)務(wù)需求,而Node則需要處理網(wǎng)絡(luò)流和文件I/O流,操作字符串遠(yuǎn)遠(yuǎn)不能滿足傳輸?shù)男阅苄枨蟆?/p>

關(guān)于Buffer的細(xì)節(jié) 后面再仔細(xì)講解一下。

所以,從這里我們可以知道,Node的內(nèi)存構(gòu)成主要由通過V8進(jìn)行分配的部分和Node自行分配的部分。受V8的垃圾回收限制的主要是V8的堆內(nèi)存。

15. 可以利用fs.readFile()和fs.writeFile()方法 來 讀寫大文件嗎?

由于V8的內(nèi)存限制,我們無法通過fs.readFile()和fs.writeFile()直接進(jìn)行大文件的操作,而改用fs.createReadStream()和fs.createWriteStream()方法通過流的方式實(shí)現(xiàn)對(duì)大文件的操作。

下面的代碼展示了如何讀取一個(gè)文件,然后將數(shù)據(jù)寫入到另一個(gè)文件的過程:

由于讀寫模型固定,上述方法有更簡潔的方式,具體如下所示:

圖片可讀流提供了管道方法pipe(),封裝了data事件和寫入操作。通過流的方式,上述代碼不會(huì)受到V8內(nèi)存限制的影響,有效地提高了程序的健壯性。如果不需要進(jìn)行字符串層面的操作,則不需要借助V8來處理,可以嘗試進(jìn)行純粹的Buffer操作,這不會(huì)受到V8堆內(nèi)存的限制。但是這種大片使用內(nèi)存的情況依然要小心,即使V8不限制堆內(nèi)存的大小,物理內(nèi)存依然有限制。

14. 你知道Buffer對(duì)象嗎?Buffer對(duì)象是通過V8分配內(nèi)存的嗎?

知道。他不是。

為何Buffer對(duì)象并非通過V8分配?這在于Node并不同于瀏覽器的應(yīng)用場景。在瀏覽器中,JavaScript直接處理字符串即可滿足絕大多數(shù)的業(yè)務(wù)需求,而Node則需要處理網(wǎng)絡(luò)流和文件I/O流,操作字符串遠(yuǎn)遠(yuǎn)不能滿足傳輸?shù)男阅苄枨蟆?/p>

關(guān)于Buffer的細(xì)節(jié) 后面再仔細(xì)講解一下。

所以,從這里我們可以知道,Node的內(nèi)存構(gòu)成主要由通過V8進(jìn)行分配的部分和Node自行分配的部分。受V8的垃圾回收限制的主要是V8的堆內(nèi)存。

15. 可以利用fs.readFile()和fs.writeFile()方法 來 讀寫大文件嗎?

由于V8的內(nèi)存限制,我們無法通過fs.readFile()和fs.writeFile()直接進(jìn)行大文件的操作,而改用fs.createReadStream()和fs.createWriteStream()方法通過流的方式實(shí)現(xiàn)對(duì)大文件的操作。

下面的代碼展示了如何讀取一個(gè)文件,然后將數(shù)據(jù)寫入到另一個(gè)文件的過程:

由于讀寫模型固定,上述方法有更簡潔的方式,具體如下所示:

可讀流提供了管道方法pipe(),封裝了data事件和寫入操作。通過流的方式,上述代碼不會(huì)受到V8內(nèi)存限制的影響,有效地提高了程序的健壯性。如果不需要進(jìn)行字符串層面的操作,則不需要借助V8來處理,可以嘗試進(jìn)行純粹的Buffer操作,這不會(huì)受到V8堆內(nèi)存的限制。但是這種大片使用內(nèi)存的情況依然要小心,即使V8不限制堆內(nèi)存的大小,物理內(nèi)存依然有限制。

 

責(zé)任編輯:武曉燕 來源: 前端陽光
相關(guān)推薦

2021-06-18 09:17:10

探究Node前端開發(fā)

2021-06-12 18:37:56

Nodejs前端開發(fā)

2021-07-08 09:48:01

NodeBuffer亂碼

2009-09-25 16:40:49

UPS常識(shí)

2010-01-27 13:41:21

2025-01-02 14:50:34

MyBatis開發(fā)緩存

2022-02-15 11:49:08

eBPFGo內(nèi)存

2011-12-22 14:27:11

2013-07-15 11:03:52

802.11ac技術(shù)802.11ac

2021-01-19 05:24:36

ThreadLocal線程編程

2009-11-27 10:37:41

GPRS路由

2010-02-04 16:52:01

多層交換技術(shù)

2009-11-12 14:32:00

BGP路由協(xié)議

2021-07-12 07:08:52

TCP協(xié)議面試

2009-12-09 10:07:19

Linux靜態(tài)路由

2010-08-04 09:43:28

Flex應(yīng)用程序

2010-11-29 11:22:36

SYBASE數(shù)據(jù)庫日志

2013-12-23 09:25:21

2009-12-09 13:35:09

靜態(tài)路由配置

2009-11-20 09:56:27

軟交換路由技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)