亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Node怎么最小化堆分配和防止內存泄漏

發布時間:2023-01-13 17:53:53 來源:億速云 閱讀:127 作者:iii 欄目:web開發

這篇文章主要介紹“Node怎么最小化堆分配和防止內存泄漏”,在日常操作中,相信很多人在Node怎么最小化堆分配和防止內存泄漏問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Node怎么最小化堆分配和防止內存泄漏”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

內存管理問題在計算機領域中一直備受關注。在計算機中運行的每個軟件,都會被分配到計算機有限內存的一小部分。這些內存必須得認真管理,在合適的時間進行分配或者釋放。

Nodejs 可以通過其高效的自動垃圾回收機制,來處理內存管理的繁瑣任務,從而將開發人員解放出來,從事其他任務。雖然說 Nodejs 已經幫助開發者解決了內存管理的問題,但是在面對大型應用開發的過程中,對于開發者理解 V8Nodejs 中的內存管理機制仍然非常重要。

這片文章主要介紹了如何在堆中分配和釋放內存,并且幫助你知道如何最小化堆分配和防止內存泄漏。

Nodejs 中的堆分配

JavaScriptNode.js 為你抽象了很多東西,并且在后臺完成了大部分繁重的工作。

我們知道,當一段代碼被執行的時候,代碼中的變量和對象會被存儲在棧內存或者堆內存中,JavaScript 代碼會被存儲在將要被執行的執行上下文中。

ECMAScript 規范本身并沒有規定如何分配和管理內存。這是一個依賴于 JavaScript 引擎和底層系統架構的實現細節。深入理解引擎是如何處理變量的已經超出了本文的范圍,但如果你想了解更多關于V8是如何做到這一點的,請參考文章JavaScript內存模型揭秘數據是如何存儲在V8 JS引擎內存中的?

為什么在 Node.js 中高效的堆內存使用很重要?

存儲在堆中的內存變量將一直存在,除非它被垃圾收集器刪除或釋放。堆內存是一大塊連續的內存塊,即使再被分配和釋放之后,仍然會保持這種狀態。

不幸的是,由于堆內存收集和釋放方式,內存可能會被浪費,從而導致泄漏。

V8 使用的是分代垃圾收集機制,即它將對象劃分為不同的代(新生代和老生代)。代空間又會被劃分為不同的區——例如新生代由新空間組成,老生代會被劃分為舊空間、映射空間和大對象空間。新對象最初被分配到新生代空間中,當新生代空間使用完時,垃圾收集器將執行清理機制以釋放空間。在一次 GC 運行中幸存下來的對象會被復制到新生代的中間中間中,在第二輪運行中幸存下來的對象會被移動到老生代中。

由于運行程序先進行內存收集,占用了寶貴的虛擬內存資源,因此當不再需要內存時,程序必須釋放內存,這就是內存釋放。

此外,如果內存被釋放了(不管先前它在堆中的哪個位置釋放),堆內存將被合并為一個連續的內存塊形式。由于堆內存復雜性的增加,在這里存儲會導致更高的性能開銷(但使得后續的存儲有了更大的靈活性)。

雖然 Nodejs 擁有高效的垃圾回收機制,但是堆內存的低效使用可能導致內存泄漏。應用程序可能會占用太多的內存,甚至崩潰。

Nodejs 堆內存泄漏的原因

垃圾回收器會尋找并釋放孤立的內存空間,但有時它可能無法跟蹤每一塊內存。這可能導致不必要的負載增加,特別是對于大型應用程序。稍后我們將詳細討論 Nodejs 中的垃圾收集器是如何工作的。

導致內存泄漏的一些最常見的原因包括:

  • 多重引用

  • 全局變量

  • 閉包

  • 計時器

  • 事件

使用多個變量指針保持對一個對象的引用是非常常見的操作。雖然這對你來說非常方便,但如果對對象的其中一個引用被垃圾回收器收集,而其他引用沒有被收集,則也可能導致內存泄漏。

Node.jsJavaScript 應用程序中,被忘記清理的計時器和回調函數也是導致內存泄漏的兩個常見原因。被綁定到計時器的對象直到超時才會被垃圾收集。如果計時器一直運行,則被引用的對象將永遠不會被垃圾回收器收集。即使沒有變量指針引用對象,也會發生這種情況,因此將在堆中造成內存泄漏。

思考下示例代碼:

const language = () => {
  console.log("Javascript");】
  // 遞歸自身
  setTimeout(() => language(), 1000);
}

上面這段代碼將會被一直運行,并且永遠不會被垃圾回收器回收

如何發現 Nodejs 中的內存泄漏

這有幾個工具可以用于檢測和調試 Nodejs 中的內存泄漏,包括 Chrome DevToolsNode 的進程。memoryUsage APIAppSignal 的垃圾收集器看板。

使用 Chrome DevTools

Chrome DevTools可能是最簡單的工具之一。要啟動調試器,需要以 inspect 模式啟動 Node。運行node --inspect來執行此操作。

更具體地說,如果你的 Node 的入口是 app.js,你需要運行 node --inspect app.js 來調試Node 應用程序。然后,打開 Chromium 瀏覽器,進入 chrome://inspect。你也可以在 Edge://inspect 打開檢查器頁面。在檢查器頁面,你應該看到這樣一個頁面:

Node怎么最小化堆分配和防止內存泄漏

注意,你正在嘗試調試的 Node 應用程序出現在檢查器頁面的底部。單擊 inspect 打開調試器。調試器有兩個重要的選項卡—— MemoryProfiler ——但在本討論中,我們將重點關注 Memory 選項卡。

Node怎么最小化堆分配和防止內存泄漏

使用 Chrome 調試器查找內存泄漏最簡單的方法是使用堆快照。快照可以幫助你檢查一些變量或檢查它們的保留區大小。

你也可以通過比較多張快照發現內存泄漏。對于一個實力來說,你可以在內存泄漏之前和之后分別保存一張快照,然后比較兩者。為了獲取快照,你可以通過在 Heap snapshot 上點擊一下,然后點擊 *Take snapshot 按鈕。這可能需要一些時間,這取決于應用程序的 Total JS 堆大小。你也可以通過點擊 DevTool 底部的 load 按鈕來加載現有的快照。

當你有了兩張或者多張快照時,你就可以非常容易的比較堆分配,已找到內存泄漏的原因。你可以通過以下方式查看快照:

  • Summary:根據構造函數名稱對 Node 應用程序中的對象進行分組展示

  • Comparison: 顯示兩張快照之間的區別

  • Containment:允許你查看堆內并分析全局名稱空間中引用的對象

  • Statistics

Node怎么最小化堆分配和防止內存泄漏

DevTools 堆分析器中有兩列很突出——即 Shallow SizeRetained Size

Shallow Size 表示的是對象自身在內存中的大小。這個內存大小對于大多數對象來說并不大,但數組和字符串類型除外。另一方面, Retained Size 是黨有問題的對象和依賴對象被釋放或從根節點無法訪問時釋放的內存大小。

Chrome DevTools 并不是獲取堆快照的唯一方法。如果你使用的是 nodejs 12.0 或更高版本,你還可以通過運行  node --heapsnapshot-signal 命令:

node --heapsnapshot-signal=SIGUSR2 app.js

雖然可以使用任何標志,但建議使用用戶定義的信號SIGUSR1SIGUSR2

如果你從正在服務端運行的應用中獲取一張對快照,則可以使用 V8 包中的 writeHeapSnapshot 函數:

require("v8").writeHeapSnapshot();

這個方法要求 Nodejs 的版本高于 11.13。在早期的版本中,你可以使用相關的包來實現。

使用 Chrome DevTools 獲取堆快照并不是調試內存問題的唯一方法。你也可以使用Allocation instrumentation on timeline 跟蹤每個堆分配的情況。

內存分配時間軸顯示了隨時間變化的測量內存分配的情況。要啟用此功能,需要先啟動分析器(Profiler),然后運行應用程序示例以開始調試內存問題。如果你希望記錄長時間運行的內存分配操作,并想要更小的性能開銷,那么最好的選擇是分配抽樣方法。

通過 Nodeprocess.memoryUsage API

你也可以使用 Nodeprocess.memoryUsage API來觀察內存使用情況。運行 process.memoryUsage,你可以訪問以下內容:

  • rss:已分配的內存量

  • heapTotal:已分配堆的總大小

  • heapUsed:當執行進程時被使用內存總量

  • arrayBuffers:為 Buffer 實例分配的內存大小

使用 AppSignal 的垃圾收集器看板

為了可視化堆的變化情況,AppSignal 提供了一個方便的垃圾收集看板。當你將 Node.js 應用連接到AppSignal 時,這個看板會自動為你生成!

看看這個例子,在“V8 Heap Statistics”圖表中,你可以清楚地看到內存使用的峰值:

Node怎么最小化堆分配和防止內存泄漏

如果看板中中的數據出現一個穩定增長的趨勢,這意味著你的代碼中或者依賴中存在內存泄漏的情況。

了解更多關于 Node.js 的AppSignal。

垃圾回收機制工作原理

如果你知道如何發現內存泄漏,但如何修復它們?我們可能很快就知道。但是首先重要的是理解 NodejsV8 是如何進行垃圾收集的。

垃圾回收機制會在不需要的時候釋放內存。為了更高效的工作,垃圾回收算法必須正確的定義和識別不需要再內存中繼續存儲的內容。

在引用計數 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,那么你可以在 V8Chrome 中將性能提高 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 清除計時器是至關重要的。

當你不再需要使用 setTimeoutsetimmediation 創建計時器時,調用 clearTimeoutclearImmediate 也是一個很好的實踐。

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,但 Person1Person2 都被保存在作用域中。

這會消耗更多內存,并造成內存泄漏。為此,在面臨上面這種情況時,你最好僅聲明你需要的,將不需要的重置為 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怎么最小化堆分配和防止內存泄漏”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

镶黄旗| 襄樊市| 巴林右旗| 鲁山县| 南丰县| 通州区| 江孜县| 闵行区| 乐业县| 彩票| 德阳市| 鸡泽县| 绥芬河市| 墨脱县| 乌兰察布市| 麦盖提县| 吕梁市| 五寨县| 连云港市| 勃利县| 清流县| 兰考县| 明溪县| 阿尔山市| 棋牌| 湄潭县| 泉州市| 克什克腾旗| 峨边| 宝兴县| 巫山县| 河南省| 镇宁| 武宁县| 湾仔区| 明溪县| 贵南县| 遂昌县| 嘉鱼县| 合阳县| 拜城县|