Node學習如何最小化堆分配和防止記憶體洩漏
Node.js如何查看記憶體洩漏?以下這篇文章帶大家了解Nodejs堆分配,介紹如何最小化堆分配和防止記憶體洩漏,希望對大家有幫助!
記憶體管理問題在電腦領域中一直備受關注。在電腦中運行的每個軟體,都會被分配到電腦有限記憶體的一小部分。這些記憶體必須得認真管理,在適當的時間進行分配或釋放。
Nodejs
可以透過其高效的自動垃圾回收機制,來處理記憶體管理的繁瑣任務,從而將開發人員解放出來,從事其他任務。雖然說Nodejs
已經幫助開發者解決了記憶體管理的問題,但在面對大型應用程式開發的過程中,對於開發者理解V8
和Nodejs
中的記憶體管理機制仍然非常重要。
這片文章主要介紹如何在堆中分配和釋放內存,並且幫助你知道如何最小化堆分配和防止內存洩漏。 【相關教學推薦:nodejs影片教學、程式設計教學】
Nodejs
中的堆疊分配
JavaScript
和Node.js
為你抽象化了許多東西,並且在後台完成了大部分繁重的工作。
我們知道,當一段程式碼被執行的時候,程式碼中的變數和物件會被儲存在堆疊記憶體或堆疊記憶體中,JavaScript
程式碼會被儲存在將要執行的執行上下文中。
ECMAScript
規格本身並沒有規定如何分配和管理記憶體。這是一個依賴 JavaScript
引擎和底層系統架構的實作細節。深入理解引擎是如何處理變數的已經超出了本文的範圍,但如果你想了解更多關於V8
是如何做到這一點的,請參考文章JavaScript記憶體模型揭秘
和資料是如何儲存在V8 JS引擎記憶體中的?
。
為什麼在Node.js
中高效的堆記憶體使用很重要?
##儲存在堆中的記憶體變數會一直存在,除非它被垃圾收集器刪除或釋放。堆記憶體是一大塊連續的記憶體區塊,即使再被分配和釋放之後,仍然會保持這種狀態。 不幸的是,由於堆記憶體收集和釋放方式,記憶體可能會被浪費,導致洩漏。
V8 使用的是分代垃圾收集機制,即它將物件劃分為不同的代(新生代和老生代)。代空間又會被劃分為不同的區域-例如新生代由新空間組成,老生代會被劃分為舊空間、映射空間和大物件空間。新物件最初被分配到新生代空間中,當新生代空間使用完時,垃圾收集器將執行清理機制以釋放空間。在一次
GC 運行中倖存下來的物件會被複製到新生代的中間中間中,在第二輪運行中倖存下來的物件會被移動到老生代中。
Nodejs 擁有高效率的垃圾回收機制,但是堆疊記憶體的低效率使用可能導致記憶體洩漏。應用程式可能會佔用太多的內存,甚至崩潰。
Nodejs 堆記憶體洩漏的原因
垃圾回收器會尋找並釋放孤立的記憶體空間,但有時它可能無法追蹤每一塊記憶體。這可能導致不必要的負載增加,特別是對於大型應用程式。稍後我們將詳細討論Nodejs 中的垃圾收集器是如何運作的。
- 多重引用
- 全域變數
- 閉包
- 計時器
- 事件
使用多個變數指標保持對一個物件的參考是非常常見的操作。雖然這對你來說非常方便,但如果對物件的其中一個引用被垃圾回收器收集,而其他引用沒有被收集,則也可能導致記憶體洩漏。
在 Node.js
和 JavaScript
應用程式中,被忘記清理的計時器和回呼函數也是導致記憶體洩漏的兩個常見原因。被綁定到計時器的物件直到逾時才會被垃圾收集。如果計時器一直運行,則被引用的物件將永遠不會被垃圾回收器收集。即使沒有變數指標引用對象,也會發生這種情況,因此將在堆中造成記憶體洩漏。
思考下範例程式碼:
const language = () => { console.log("Javascript");】 // 递归自身 setTimeout(() => language(), 1000); }
上面這段程式碼將會被一直運行,並且永遠不會被垃圾回收器回收
##如何發現Nodejs 中的記憶體洩漏
這有幾個工具可以用來偵測和偵錯Nodejs 中的記憶體洩漏,包括
Chrome DevTools ,
Node 的進程。
memoryUsage API 和
AppSignal 的垃圾收集器看板。
使用 Chrome DevTools
Chrome DevTools可能是最簡單的工具之一。要啟動偵錯器,需要以
inspect 模式啟動
Node。執行
node --inspect來執行此操作。
Node 的入口是
app.js,你需要執行
node --inspect app.js來調試Node 應用程式。然後,開啟
Chromium 瀏覽器,進入
chrome://inspect。你也可以在 Edge://inspect 開啟檢查器頁面。在檢查器頁面,你應該看到這樣一個頁面:
Node 應用程式出現在檢查器頁面的底部。點選
inspect 開啟偵錯器。偵錯器有兩個重要的選項卡—
Memory 和
Profiler —但在本討論中,我們將重點放在
Memory 選項卡。
Chrome 偵錯器尋找記憶體洩漏最簡單的方法是使用
堆快照。快照可以幫助你檢查一些變數或檢查它們的保留區大小。
Heap snapshot 上點擊一下,然後點擊 *Take snapshot
按鈕。這可能需要一些時間,這取決於應用程式的 Total JS
堆大小。你也可以點選
DevTool 底部的
load 按鈕來載入現有的快照。
Summary
:根據建構函式名稱對Node
應用程式中的物件進行分組展示
Comparison
#:顯示兩張快照之間的差異
-
Containment
:允許你查看堆內並分析全域名稱空間中引用的物件
Statistics
DevTools 堆疊分析器中有兩列很突出-即
Shallow Size 和Retained Size
。
Shallow Size 表示的是物件本身在記憶體中的大小。這個記憶體大小對於大多數物件來說並不大,但數組和字串類型除外。另一方面, Retained Size
是黨有問題的物件和依賴物件被釋放或從根節點無法存取時釋放的記憶體大小。
Chrome DevTools 並不是取得堆疊快照的唯一方法。如果你使用的是
nodejs 12.0 或更高版本,你也可以透過執行
node --heapsnapshot-signal 指令:
node --heapsnapshot-signal=SIGUSR2 app.js
SIGUSR1或
SIGUSR2。
V8 套件中的
writeHeapSnapshot 函數:
require("v8").writeHeapSnapshot();
这个方法要求 Nodejs
的版本高于 11.13。在早期的版本中,你可以使用相关的包来实现。
使用 Chrome DevTools
获取堆快照并不是调试内存问题的唯一方法。你也可以使用Allocation instrumentation on timeline
跟踪每个堆分配的情况。
内存分配时间轴显示了随时间变化的测量内存分配的情况。要启用此功能,需要先启动分析器(Profiler
),然后运行应用程序示例以开始调试内存问题。如果你希望记录长时间运行的内存分配操作,并想要更小的性能开销,那么最好的选择是分配抽样方法。
通过 Node
的 process.memoryUsage
API
你也可以使用 Node
的 process.memoryUsage
API来观察内存使用情况。运行 process.memoryUsage
,你可以访问以下内容:
rss
:已分配的内存量heapTotal
:已分配堆的总大小heapUsed
:当执行进程时被使用内存总量arrayBuffers
:为 Buffer 实例分配的内存大小
使用 AppSignal
的垃圾收集器看板
为了可视化堆的变化情况,AppSignal
提供了一个方便的垃圾收集看板。当你将 Node.js
应用连接到AppSignal
时,这个看板会自动为你生成!
看看这个例子,在“V8 Heap Statistics
”图表中,你可以清楚地看到内存使用的峰值:
如果看板中中的数据出现一个稳定增长的趋势,这意味着你的代码中或者依赖中存在内存泄漏的情况。
垃圾回收机制工作原理
如果你知道如何发现内存泄漏,但如何修复它们?我们可能很快就知道。但是首先重要的是理解 Nodejs
和 V8
是如何进行垃圾收集的。
垃圾回收机制会在不需要的时候释放内存。为了更高效的工作,垃圾回收算法必须正确的定义和识别不需要再内存中继续存储的内容。
在引用计数 GC
算法中,如果堆中的对象在堆栈中不再有引用,则该对象将被垃圾收集。该算法通过计数引用来工作——因此,如果引用计数为零,则对象将进行垃圾收集。尽管这个算法大多数时候都有效,但它在处理循环引用的情况时却失效了。
看一下代码示例:
let data = {}; data.el = data; let obj1 = {}; let obj2 = {}; obj1.a = obj2; obj2.a = obj1;
具有循环引用的对象永远不会被清除作用域或被垃圾回收器回收,即使不再需要或使用它们。这会形成内存泄漏,并使应用程序效率低下。值得庆幸的是,Node.js
不再使用这种算法进行垃圾回收。
JavaScript
中的最上层对象是一个全局对象。在浏览器中,是 window
对象,但在 Nodejs
中,是 global
对象。该算法比引用计数算法更高效,并解决了循环引用的问题。
考虑到上面的例子,虽然 obj1
和 obj2 仍然
存在循环引用,但如果它们不再从顶级对象可访问(不再需要),它们将被垃圾收集。
这种算法,通常称为 mark and sweep
(标记清除算法)回收算法,非常有用。但是,你必须小心并显式地使一个对象从根节点不可访问,以确保它被垃圾收集。
修复 Nodejs App 中的内存泄漏
这有一些方法可以提高内存使用率并避免内存泄漏。
避免全局变量
全局变量包括使用 var
关键字声明的变量、this
关键字声明的变量和未使用关键字声明的变量。
我们已经偶然声明的全局变量(以及任何其他形式的全局变量)会导致内存泄漏。它们总是可以从全局对象访问,因此除非显式地设置为 null
,否则不能被垃圾收集。
考虑下面的例子:
function variables() { this.a = "Variable one"; var b = "Variable two"; c = "Variable three"; }
这三个变量都是全局变量。为了避免使用全局变量,可以考虑在文件顶部添加 use strict
指令来切换strict
模式。
使用 JSON.parse
JSON
的语法比 JavaScript
简单得多,因此它比 JavaScript
对象更容易解析。
事实上,如果你使用一个大型 JavaScript
对象,通过将其转化为字符串形式,使用时解析为 JSON
,那么你可以在 V8
和Chrome
中将性能提高 1.7 倍。
在其他 JavaScript
引擎(如Safari
)中,性能可能会更好。在 Webpack
中使用这种优化方法来提高前端应用程序的性能。
例如,不使用以下 JavaScript
对象:
const Person = { name: "Samuel", age: 25, language: "English" };
更有效的方法是将它们进行字符串化,然后将其解析为JSON
。
const Person = JSON.parse('{"name":"Samuel","age":25,"language":"English"}');
将大数据处理拆分为块并创建子进程
你获取在实际业务中会当处理大型数据时,遇到一些奇观的内存溢出的问题,例如大的 CSV
文件。当然,你可以通过扩展你的应用内存上限去处理任务,但是最好的方法是通过将大块数据分割为多个小块(chunks
)。
在一些情况下,在多核机器上扩展 Node.js
应用程序可能会有所帮助。这涉及到将应用程序分离为主进程和工作进程。worker
处理繁重的逻辑,而 master
控制 worker
并在内存耗尽时重新启动它们。
有效使用计时器
我们创建的计时器可能会造成内存泄漏。为了提高堆内存管理,确保你的计时器不会永远运行。
特别是,使用 setInterval
创建计时器时,当不再需要计时器时调用 clearInterval
清除计时器是至关重要的。
当你不再需要使用 setTimeout
或 setimmediation
创建计时器时,调用 clearTimeout
或clearImmediate
也是一个很好的实践。
const timeout = setTimeout(() => { console.log("timeout"); }, 1500); const immediate = setImmediate(() => { console.log("immediate"); }); const interval = setInterval(() => { console.log("interval"); }, 500); clearTimeout(timeout); clearImmediate(immediate); clearInterval(interval);
移除闭包中不在需要的变量
在 JavaScript
中,闭包是一个常见概念。例如存在函数嵌套或者回调函数。如果在函数中使用了一个变量,当函数返回时,它将被标记为垃圾收集,但闭包可不是这样的。
代码示例:
const func = () => { let Person1 = { name: "Samuel", age: 25, language: "English" }; let Person2 = { name: "Den", age: 23, language: "Dutch" }; return () => Person2; };
上面函数会一直引用父级作用域并将每个变量保存在作用域中。换句话说,虽然你仅仅使用了 Person2
,但 Person1
和 Person2
都被保存在作用域中。
这会消耗更多内存,并造成内存泄漏。为此,在面临上面这种情况时,你最好仅声明你需要的,将不需要的重置为 null
。
例如:
const func = () => { let Person1 = { name: "Samuel", age: 25, language: "English" }; let Person2 = { name: "Den", age: 23, language: "Dutch" }; Person1 = null; return () => Person2; };
取消订阅观察者和 Event Emitters
具有较长生命周期的观察器和事件发射器可能是内存泄漏的来源,特别是如果你在不再需要它们时没有取消订阅的话。
代码示例:
const EventEmitter = require("events").EventEmitter; const emitter = new EventEmitter(); const bigObject = {}; //Some big object const listener = () => { doSomethingWith(bigObject); }; emitter.on("event1", listener);
在这里,我们保留 bigObject
的内存,直到侦听器从发射器中释放,或者发射器被垃圾收集。为了解决这个问题,我们需要调用 removeEventListener
从发射器中释放监听器。
emitter.removeEventListener("event1", listener);
当连接到发射器的事件侦听器超过 10 个时,也可能发生内存泄漏。大多数情况下,你可以通过编写更高效的代码来解决这个问题。
但是,在某些情况下,你可能需要显式地设置最大事件侦听器。
例如:
emitter.setMaxListeners(n);
总结
在这篇文章中,我们探索了如何最小化你的堆和检测 Node.js
中的内存泄漏。
我们首先研究了 Node
中的堆分配,包括堆栈和堆的工作方式。然后,我们考虑了跟踪内存使用情况和内存泄漏的原因的重要性。
接下来,我们看到了如何使用 Chrome DevTools ,
Node
的进程来查找内存泄漏。memoryUsage
API和 AppSignal
的垃圾收集可视化看板。
最后,我们发现了垃圾收集是如何工作的,并分享了一些修复应用程序内存泄漏的方法。
像其他程式語言一樣,記憶體管理在 JavaScript
和 Node.js
中非常重要。我希望這篇介紹對你有用。編碼快樂!
##更多node相關知識,請訪問:
nodejs 教程!
以上是Node學習如何最小化堆分配和防止記憶體洩漏的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

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

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

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

Dreamweaver CS6
視覺化網頁開發工具

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

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Django:前端和後端開發都能搞定的神奇框架! Django是一個高效、可擴展的網路應用程式框架。它能夠支援多種Web開發模式,包括MVC和MTV,可以輕鬆地開發出高品質的Web應用程式。 Django不僅支援後端開發,還能夠快速建構出前端的介面,透過模板語言,實現靈活的視圖展示。 Django把前端開發和後端開發融合成了一種無縫的整合,讓開發人員不必專門學習

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數
