聊聊Node.js中的 GC (垃圾回收)機制
Node 是如何做 GC (垃圾回收)的?下面這篇文章就來帶大家了解一下。
GC,Garbage Collection,垃圾回收。在程式設計中,一般指的是記憶體自動回收機制,會定時將不需要用到的資料進行清除。
Node.js 底層使用了 V8 引擎。 V8 是 Google 開源的高效能 JavaScript 引擎,使用了 C 來編寫。 【相關教學推薦:nodejs影片教學】
Node.js 的記憶體主要分成三個部分:
程式碼空間:存放程式碼段的地方;
堆疊:函數呼叫堆疊產生的臨時變量,為一些基本類型,例如數字、字串、布林值,以及物件參考(保存的是地址,不保存對象本身)。
堆:存放物件等資料;
#堆記憶體
Node.js 底層使用的是V8,下面來講解V8 的記憶體回收機制。
首先 JS 中所有的物件都會保存在堆記憶體中。在創建進程的時候,會分配一個初始大小的堆內存,然後我們的物件就會放到裡面。
當物件越來越多,堆記憶體會不夠用,此時堆記憶體會動態地擴大。如果到達一個最大限制(現在通常是 4GB),就會堆疊記憶體溢位的錯誤,然後終止 Node.js 進程。
新生代與老生代
V8 先將記憶體分成兩部分,或說兩個生代(generation):
-
新生代(yong generation):保存一些存活時間較短的物件;
老生代(old generation):保存存活時間長或長駐的物件。
新生代很小,這裡會存放一些存活時間很短的對象,通常它們會被頻繁地回收(例如函數的呼叫堆疊的一些臨時物件)。
新生代可透過
node --max-semi-space-size=SIZE index.js
修改新生代的大小,單位為 MB。另外,老生代則透過
--max-old-space-size=SIZE
來設定
新生代的Scavenge 演算法
新生代使用了Scavenge 演算法,是一種基於copy(複製)的演算法。
新生代會分成兩個空間,這個空間稱為semispace,它們為:
-
From 空間:新宣告的物件會放入這裡
To 空間:用作移動的空間
新宣告的物件會放入到From 空間中,From 空間的物件緊密排布,透過指針,上一對象緊貼下一個對象,是記憶體連續的,不用擔心記憶體碎片的問題。
所謂記憶體碎片,指的是空間分配不均勻,產生大量小的連續空間,無法放入一個大物件。
當 From 空間快滿了,我們就會遍歷找出活躍對象,將它們 copy 到 To 空間。此時 From 空間其實就空了,然後我們將 From 和 To 互換身分。
如果有些物件被 copy 了多次,會被認為存活時間較長,將會被移到老生代。
這種基於copy 的演算法,優點是可以很好地處理記憶體碎片的問題,缺點是會浪費一些空間作為搬運的空間位置,此外因為拷貝比較耗費時間,所以不適合分配太大的記憶體空間,更多是做一種輔助GC。
Mark-Sweep 和Mark-Compact
老生代的空間就比新生代大得多了,放的是一些存活時間長的對象,用的是Mark-Sweep (標記清除)演算法。
首先是標記階段。從根集 Root Set(執行堆疊和全域物件)往上找到所有能存取的對象,並標記它們為活躍物件。
標記完後,就是清除階段,將沒有標記的物件清除,其實就是標記一下這個記憶體位址為空閒。
這種做法會導致 空閒記憶體空間碎片化,當我們創建了一個大的連續對象,就會找不到地方放下。這時候,就要用 Mark-Compact(標記整理)來將碎片的活躍物件做一個整合。
Mark-Compact 會將所有活躍物件拷貝移動到一端,然後邊界的另一邊就是一整塊的連續可用記憶體了。
考慮到Mark-Sweep 和Mark-Compact 花費的時間很長,而且會阻塞JavaScript 的線程,所以通常我們不會一次性做完,而是用增量標記(Incremental Marking)的方式。也就是做斷斷續續地標記,小步走,垃圾回收和應用邏輯交替進行。
另外,V8 也做了平行標記和並行清理,提高執行效率。
查看記憶體相關資訊
我們可以透過 process.memoryUsage 方法拿到記憶體相關的一些資訊。
process.memoryUsage();
#輸出內容為:
{ rss: 35454976, heapTotal: 7127040, heapUsed: 5287088, external: 958852, arrayBuffers: 11314 }
說明
rss:常駐記憶體大小(resident set size),包含程式碼片段、堆疊記憶體、棧等部分。
heapTotal:V8 的堆記憶體總大小;
heapUsed:佔用的堆記憶體;
#external:V8 以外的記憶體大小,指的是C 物件佔用的內存,例如Buffer 資料。
arrayBuffers:
ArrayBuffer
和SharedArrayBuffer
相關的記憶體大小,屬於 external 的一部份。
以上數字的單位都是位元組。
測試最大記憶體限制
寫一個腳本,用一個計時器,讓一個陣列不停地變大,並列印堆記憶體使用情況,直到記憶體溢出。
const format = function (bytes) { return (bytes / 1024 / 1024).toFixed(2) + " MB"; }; const printMemoryUsage = function () { const memoryUsage = process.memoryUsage(); console.log( `heapTotal: ${format(memoryUsage.heapTotal)}, heapUsed: ${format( memoryUsage.heapUsed )}` ); }; const bigArray = []; setInterval(function () { bigArray.push(new Array(20 * 1024 * 1024)); printMemoryUsage(); }, 500);
要特別注意的是,不要用 Buffer 來做測試。
因為 Buffer 是 Node.js 特有的處理二進制的對象,它不是在 V8 中的實現的,是 Node.js 用 C 另外實現的,不通過 V8 分配內存,屬於堆外內存。
我使用電腦是macbook pro M1 Pro,Node.js 版本為v16.17.0
,使用的V8 版本是9.4.146.26-node.22(透過process.versions .v8 得到)。
輸出結果為(省略了一些多餘的資訊):
heapTotal: 164.81 MB, heapUsed: 163.93 MB heapTotal: 325.83 MB, heapUsed: 323.79 MB heapTotal: 488.59 MB, heapUsed: 483.84 MB ... heapTotal: 4036.44 MB, heapUsed: 4003.37 MB heapTotal: 4196.45 MB, heapUsed: 4163.29 MB <--- Last few GCs ---> [28033:0x140008000] 17968 ms: Mark-sweep 4003.2 (4036.4) -> 4003.1 (4036.4) MB, 2233.8 / 0.0 ms (average mu = 0.565, current mu = 0.310) allocation failure scavenge might not succeed [28033:0x140008000] 19815 ms: Mark-sweep 4163.3 (4196.5) -> 4163.1 (4196.5) MB, 1780.3 / 0.0 ms (average mu = 0.413, current mu = 0.036) allocation failure scavenge might not succeed <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory ...
可以看到,是在 4000 MB 之後超出了記憶體上限,發生堆溢出,然後退出了進程。說明在我的機器上,預設的最大記憶體為 4G。
實際最大記憶體和它運作所在的機器有關,如果你的機器的記憶體大小為 2G,最大記憶體將設定為 1.5G。
更多node相關知識,請造訪:nodejs 教學!
以上是聊聊Node.js中的 GC (垃圾回收)機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

隨著網路技術的發展,前端開發變得日益重要。尤其是行動端設備的普及,更需要高效率、穩定、安全又易於維護的前端開發技術。而作為一門快速發展的程式語言,Go語言已經被越來越多的開發者所使用。那麼,使用Go語言進行前端開發行得通嗎?接下來,本文將為你詳細說明如何使用Go語言進行前端開發。先來看看為什麼要使用Go語言進行前端開發。很多人認為Go語言是一門

身為C#開發者,我們的開發工作通常包括前端和後端的開發,而隨著技術的發展和專案的複雜性提高,前端與後端協同開發也變得越來越重要和複雜。本文將分享一些前端與後端協同開發的技巧,以幫助C#開發者更有效率地完成開發工作。確定好介面規範前後端的協同開發離不開API介面的交互。要確保前後端協同開發順利進行,最重要的是定義好介面規格。接口規範涉及到接口的命

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

golang可以做前端,Golang是一種通用性很強的程式語言,可以用於開發不同類型的應用程序,包括前端應用程序,透過使用Golang來編寫前端,可以擺脫JavaScript等語言引起的一系列問題,例如類型安全性差、效能低下,以及程式碼難以維護等問題。

實作即時通訊的方法有WebSocket、Long Polling、Server-Sent Events、WebRTC等等。詳細介紹:1、WebSocket,它可以在客戶端和伺服器之間建立持久連接,實現即時的雙向通信,前端可以使用WebSocket API來創建WebSocket連接,並透過發送和接收訊息來實現即時通訊;2、Long Polling,是一種模擬即時通訊的技術等等
