成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

本文轉載自微信公眾號「前端陽光」,作者事業有成的張啦啦 。轉載本文請聯系前端陽光公眾號。

 1. V8是用什么給對象分配內存的呢?

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

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

4. 為什么要分代呢?

5. 哦,那你談談是怎么分代的?

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

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

8. 為什么要設置25%這個這么低的值呢?

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

10. 那老生代的對象該怎么處理?

11. 那為什么還要標記整理?

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

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

14. 你知道Buffer對象嗎?Buffer對象是通過V8分配內存的嗎?

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

1. V8是用什么給對象分配內存的呢?

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

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

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

當我們在代碼中聲明變量并賦值時,所使用對象的內存就分配在堆中。如果已申請的堆空閑內存不夠分配新的對象,將繼續申請堆內存,直到堆的大小超過V8的限制為止。

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

表層原因為V8最初為瀏覽器而設計,不太可能遇到用大量內存的場景。對于網頁來說,V8的限制值已經綽綽有余。

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

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

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

4. 為什么要分代呢?

因為在實際的應用中,對象的生存周期長短不一,不同的算法只能針對特定情況具有最好的效果。為此,現代的垃圾回收算法中按對象的存活時間將內存的垃圾回收進行不同的分代,然后分別對不同分代的內存施以更高效的算法。

5. 哦,那你談談是怎么分代的?

在V8中,主要將內存分為新生代和老生代兩代。新生代中的對象為存活時間較短的對象,老生代中的對象為存活時間較長或常駐內存的對象。圖為V8的分代示意圖。

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

在分代的基礎上,新生代中的對象主要通過Scavenge算法進行垃圾回收。是一種采用復制的方式實現的垃圾回收算法。

它將堆內存一分為二,每一部分空間稱為semispace。在這兩個semispace空間中,只有一個處于使用中,另一個處于閑置狀態。處于使用狀態的semispace空間稱為From空間,處于閑置狀態的空間稱為To空間。當我們分配對象時,先是在From空間中進行分配。當開始進行垃圾回收時,會檢查From空間中的存活對象,這些存活對象將被復制到To空間中,而非存活對象占用的空間將會被釋放。完成復制后,From空間和To空間的角色發生對換。

簡而言之,在垃圾回收的過程中,就是通過將存活對象在兩個semispace空間之間進行復制。

Scavenge的缺點是只能使用堆內存中的一半,這是由劃分空間和復制機制所決定的。但Scavenge由于只復制存活的對象,并且對于生命周期短的場景存活對象只占少部分,所以它在時間效率上有優異的表現。

由于Scavenge是典型的犧牲空間換取時間的算法,所以無法大規模地應用到所有的垃圾回收中。但可以發現,Scavenge非常適合應用在新生代中,因為新生代中對象的生命周期較短,恰恰適合這個算法。

是故,V8的堆內存示意圖應當如圖所示。

當一個對象經過多次復制依然存活時,它將會被認為是生命周期較長的對象。這種較長生命周期的對象隨后會被移動到老生代中,采用新的算法進行管理。對象從新生代中移動到老生代中的過程稱為晉升。

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

對象晉升的條件主要有兩個,一個是對象是否經歷過Scavenge回收,一個是To空間的內存占用比超過限制。

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

另一個判斷條件是To空間的內存占用比。當要從From空間復制一個對象到To空間時,如果To空間已經使用了超過25%,則這個對象直接晉升到老生代空間中,這個晉升的判斷示意圖如圖所示。

8. 為什么要設置25%這個這么低的值呢?

設置25%這個限制值的原因是當這次Scavenge回收完成后,這個To空間將變成From空間,接下來的內存分配將在這個空間中進行。如果占比過高,會影響后續的內存分配。

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

對于老生代中的對象,由于存活對象占較大比重,再采用Scavenge的方式會有兩個問題:一個是存活對象較多,復制存活對象的效率將會很低;另一個問題依然是浪費一半空間的問題。這兩個問題導致應對生命周期較長的對象時Scavenge會顯得捉襟見肘。

10. 那老生代的對象該怎么處理?

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

Mark-Sweep是標記清除的意思,它分為標記和清除兩個階段。與Scavenge相比,Mark-Sweep并不將內存空間劃分為兩半,所以不存在浪費一半空間的行為。與Scavenge復制活著的對象不同,Mark-Sweep在標記階段遍歷堆中的所有對象,并標記活著的對象,在隨后的清除階段中,只清除沒有被標記的對象??梢钥闯觯琒cavenge中只復制活著的對象,而Mark-Sweep只清理死亡對象?;顚ο笤谛律兄徽驾^小部分,死對象在老生代中只占較小部分,這是兩種回收方式能高效處理的原因。圖為Mark-Sweep在老生代空間中標記后的示意圖,黑色部分標記為死亡的對象。

11. 那為什么還要標記整理?

Mark-Sweep最大的問題是在進行一次標記清除回收后,內存空間會出現不連續的狀態。這種內存碎片會對后續的內存分配造成問題,因為很可能出現需要分配一個大對象的情況,這時所有的碎片空間都無法完成此次分配,就會提前觸發垃圾回收,而這次回收是不必要的。

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

完成移動后,就可以直接清除最右邊的存活對象后面的內存區域完成回收。

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

這里將Mark-Sweep和Mark-Compact結合著介紹不僅僅是因為兩種策略是遞進關系,在V8的回收策略中兩者是結合使用的。表是目前介紹到的3種主要垃圾回收算法的簡單對比。

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

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

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

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

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

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

14. 你知道Buffer對象嗎?Buffer對象是通過V8分配內存的嗎?

知道。他不是。

為何Buffer對象并非通過V8分配?這在于Node并不同于瀏覽器的應用場景。在瀏覽器中,JavaScript直接處理字符串即可滿足絕大多數的業務需求,而Node則需要處理網絡流和文件I/O流,操作字符串遠遠不能滿足傳輸的性能需求。

關于Buffer的細節 后面再仔細講解一下。

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

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

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

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

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

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

14. 你知道Buffer對象嗎?Buffer對象是通過V8分配內存的嗎?

知道。他不是。

為何Buffer對象并非通過V8分配?這在于Node并不同于瀏覽器的應用場景。在瀏覽器中,JavaScript直接處理字符串即可滿足絕大多數的業務需求,而Node則需要處理網絡流和文件I/O流,操作字符串遠遠不能滿足傳輸的性能需求。

關于Buffer的細節 后面再仔細講解一下。

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

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

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

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

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

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

 

責任編輯:武曉燕 來源: 前端陽光
相關推薦

2021-06-18 09:17:10

探究Node前端開發

2021-06-12 18:37:56

Nodejs前端開發

2021-07-08 09:48:01

NodeBuffer亂碼

2009-09-25 16:40:49

UPS常識

2010-01-27 13:41:21

2025-01-02 14:50:34

MyBatis開發緩存

2011-12-22 14:27:11

2013-07-15 11:03:52

802.11ac技術802.11ac

2022-02-15 11:49:08

eBPFGo內存

2010-08-04 09:43:28

Flex應用程序

2009-12-09 10:07:19

Linux靜態路由

2010-11-29 11:22:36

SYBASE數據庫日志

2010-02-04 16:52:01

多層交換技術

2009-11-27 10:37:41

GPRS路由

2009-11-12 14:32:00

BGP路由協議

2021-01-19 05:24:36

ThreadLocal線程編程

2013-12-23 09:25:21

2021-07-12 07:08:52

TCP協議面試

2009-12-09 13:35:09

靜態路由配置

2009-11-20 09:56:27

軟交換路由技術
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 蜜桃免费一区二区三区 | 成人一区二区电影 | 欧美a视频 | 国产三级精品三级在线观看四季网 | 99精品国产一区二区三区 | 精品久久久久久 | 亚洲视频二区 | 中文字幕国产高清 | 欧美一区二区二区 | 91久久国产综合久久91精品网站 | 国内av在线 | 久久免费高清 | 国产一区二区在线看 | 久久综合888| 免费在线视频一区二区 | 日韩中文字幕在线观看视频 | 特级黄色毛片 | 天天夜天天操 | 在线视频成人 | 麻豆av一区二区三区久久 | 亚洲一区影院 | 美女久久视频 | 亚洲精品欧美 | 视频羞羞| 狠狠色综合久久婷婷 | 国产乱码精品一区二区三区忘忧草 | 国产伦一区二区三区四区 | 成人在线不卡 | 一级毛片大全免费播放 | 亚洲综合色自拍一区 | av影音资源 | 天天爽天天操 | 一级片视频免费 | 青春草在线 | 日韩视频在线一区二区 | 欧美久久久电影 | 91亚洲精品国偷拍自产在线观看 | 一区二区三区四区日韩 | 日韩中文字幕av | 狠狠久| 91av在线免费看 |