中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Node.js中出現內存泄漏的原因有哪些

發布時間:2021-07-21 09:28:46 來源:億速云 閱讀:144 作者:Leah 欄目:編程語言

本篇文章給大家分享的是有關Node.js中出現內存泄漏的原因有哪些,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

內存泄漏(Memory  Leak)指由于疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。如果內存泄漏的位置比較關鍵,那么隨著處理的進行可能持有越來越多的無用內存,這些無用的內存變多會引起服務器響應速度變慢,嚴重的情況下導致內存達到某個極限(可能是進程的上限,如  v8 的上限;也可能是系統可提供的內存上限)會使得應用程序崩潰。

傳統的 C/C++ 中存在野指針,對象用完之后未釋放等情況導致的內存泄漏。而在使用虛擬機執行的語言中如 Java、JavaScript 由于使用了 GC (Garbage Collection,垃圾回收)機制自動釋放內存,使得程序員的精力得到的極大的解放,不用再像傳統語言那樣時刻對于內存的釋放而戰戰兢兢。

但是,即便有了 GC 機制可以自動釋放,但這并不意味這內存泄漏的問題不存在了。內存泄漏依舊是開發者們不能繞過的一個問題,今天讓我們來了解如何分析 Node.js 中的內存泄漏。

GC in Node.js

Node.js 使用 V8 作為 JavaScript 的執行引擎,所以討論 Node.js 的 GC 情況就等于在討論 V8 的 GC。在 V8 中一個對象的內存是否被釋放,是看程序中是否還有地方持有改對象的引用。

在 V8 中,每次 GC 時,是根據 root 對象 (瀏覽器環境下的 window,Node.js 環境下的 global )  依次梳理對象的引用,如果能從 root 的引用鏈到達訪問,V8  就會將其標記為可到達對象,反之為不可到達對象。被標記為不可到達對象(即無引用的對象)后就會被 V8 回收。更多細節,可以參見 alinode  的 解讀 V8 GC。

了解上述的點之后,你就會知道,在 Node.js 中內存泄露的原因就是本該被清除的對象,被可到達對象引用以后,未被正確的清除而常駐內存。

內存泄漏的幾種情況

一、全局變量

a = 10;//未聲明對象。global.b = 11;//全局變量引用

這種比較簡單的原因,全局變量直接掛在 root 對象上,不會被清除掉。

二、閉包

function out() {  const bigData = new Buffer(100);
  inner = function () {void bigData;
  }
}

閉包會引用到父級函數中的變量,如果閉包未釋放,就會導致內存泄漏。上面例子是 inner 直接掛在了 root 上,那么每次執行 out 函數所產生的 bigData 都不會釋放,從而導致內存泄漏。

需要注意的是,這里舉得例子只是簡單的將引用掛在全局對象上,實際的業務情況可能是掛在某個可以從 root 追溯到的對象上導致的。

三、事件監聽

Node.js 的事件監聽也可能出現的內存泄漏。例如對同一個事件重復監聽,忘記移除(removeListener),將造成內存泄漏。這種情況很容易在復用對象上添加事件時出現,所以事件重復監聽可能收到如下警告:

(node:2752) Warning: Possible EventEmitter memory leak  detected。11 haha listeners added。Use emitter。setMaxListeners() to  increase limit

例如,Node.js 中 Agent 的 keepAlive 為 true 時,可能造成的內存泄漏。當 Agent keepAlive 為  true 的時候,將會復用之前使用過的 socket,如果在 socket 上添加事件監聽,忘記清除的話,因為 socket  的復用,將導致事件重復監聽從而產生內存泄漏。

原理上與前一個添加事件監聽的時候忘了清除是一樣的。在使用 Node.js 的 http 模塊時,不通過 keepAlive 復用是沒有問題的,復用了以后就會可能產生內存泄漏。所以,你需要了解添加事件監聽的對象的生命周期,并注意自行移除。

關于這個問題的實例,可以看 Github 上的 issues(node Agent keepAlive 內存泄漏)

四、其他原因

還有一些其他的情況可能會導致內存泄漏,比如緩存。在使用緩存的時候,得清楚緩存的對象的多少,如果緩存對象非常多,得做限制***緩存數量處理。還有就是非常占用  CPU 的代碼也會導致內存泄漏,服務器在運行的時候,如果有高 CPU 的同步代碼,因為Node.js  是單線程的,所以不能處理處理請求,請求堆積導致內存占用過高。

定位內存泄漏

一、重現內存泄漏情況

想要定位內存泄漏,通常會有兩種情況:

  1. 對于只要正常使用就可以重現的內存泄漏,這是很簡單的情況只要在測試環境模擬就可以排查了。

  2. 對于偶然的內存泄漏,一般會與特殊的輸入有關系。想穩定重現這種輸入是很耗時的過程。如果不能通過代碼的日志定位到這個特殊的輸入,那么推薦去生產環境打印內存快照了。需要注意的是,打印內存快照是很耗 CPU 的操作,可能會對線上業務造成影響。

快照工具推薦使用 heapdump 用來保存內存快照,使用 devtool 來查看內存快照。使用 heapdump 保存內存快照時,只會有  Node.js 環境中的對象,不會受到干擾(如果使用 node-inspector 的話,快照中會有前端的變量干擾)。

PS:安裝 heapdump 在某些 Node.js 版本上可能出錯,建議使用 npm install heapdump -target=Node.js 版本來安裝。

二、打印內存快照

將 heapdump 引入代碼中,使用 heapdump.writeSnapshot 就可以打印內存快照了了。為了減少正常變量的干擾,可以在打印內存快照之前會調用主動釋放內存的 gc() 函數(啟動時加上 –expose-gc 參數即可開啟)。

const heapdump = require('heapdump');const save = function () {
  gc();
  heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot');
}

在打印線上的代碼的時候,建議按照內存增長情況來打印快照。heapdump 可以使用 kill 向程序發送信號來打印內存快照(只在 *nix 系統上提供)。

kill -USR2 <pid>

推薦打印 3 個內存快照,一個是內存泄漏之前的內存快照,一個是少量測試以后的內存快照,還有一個是多次測試以后的內存快照。

***個內存快照作為對比,來查看在測試后有哪些對象增長。在內存泄漏不明顯的情況下,可以與大量測試以后的內存快照對比,這樣能更容易定位。

三、對比內存快照找出泄漏位置

通過內存快照找到數量不斷增加的對象,找到增加對象是被誰給引用,找到問題代碼,改正之后就行,具體問題具體分析,這里通過我們在工作中遇到的情況來講解。

const {EventEmitter} = require('events');const heapdump = require('heapdump');

global.test = new EventEmitter();
heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot');function run3() {  const innerData = new Buffer(100);  const outClosure3 = function () {void innerData;
  };
  test.on('error', () => {console.log('error');
  });
  outClosure3();
}for(let i = 0; i < 10; i++) {
  run3();
}
gc();

heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot');

這里是對錯誤代碼的最小重現代碼。

首先使用 node &ndash;expose-gc index.js 運行代碼,將會得到兩個內存快照,之后打開 devtool,點擊 profile,載入內存快照。打開對比,Delta 會顯示對象的變化情況,如果對象 Delta 一直增長,就很有可能是內存泄漏了。

Node.js中出現內存泄漏的原因有哪些

可以看到有三處對象明顯增長的地方,閉包、上下文以及 Buffer 對象增長。點擊查看一下對象的引用情況:

Node.js中出現內存泄漏的原因有哪些

其實這三處對象增長都是一個問題導致的。test 對象中的 error 監聽事件中閉包引用了 innerData 對象,導致 buffer 沒有被清除,從而導致內存泄漏。

其實這里的 error 監聽事件中沒有引用 innerData 為什么會閉包引用了 innerData 對象,這個問題很是疑惑,后來弄清是 V8 的優化問題,在文末會額外講解一下。對于對比快照找到問題,得看你對代碼的熟悉程度,還有眼力了。

如何避免內存泄漏

文中的例子基本都可以很清楚的看出內存泄漏,但是在工作中,代碼混合上業務以后就不一定能很清楚的看出內存泄漏了,還是得依靠工具來定位內存泄漏。另外下面是一些避免內存泄漏的方法。

  1. ESLint 檢測代碼檢查非期望的全局變量。

  2. 使用閉包的時候,得知道閉包了什么對象,還有引用閉包的對象何時清除閉包。***可以避免寫出復雜的閉包,因為復雜的閉包引起的內存泄漏,如果沒有打印內存快照的話,是很難看出來的。

  3. 綁定事件的時候,一定得在恰當的時候清除事件。在編寫一個類的時候,推薦使用 init 函數對類的事件監聽進行綁定和資源申請,然后 destroy 函數對事件和占用資源進行釋放。

額外說明

在做了很多測試以后得到下面關于閉包的總結。

class Test{};
global.test = new Test()function run5(bigData) {  const innerData = new Buffer(100);  // 被閉包引用,創建一個 context: context1。
  // context1 引用 bigData,innerData。
  // closure 為 function run5()
  // run5函數沒有 context,所以 context1 沒有previous。
  // 在 run5中新建的函數將綁定上 context1。

  test.outClosure5 = function () {// 此函數閉包 context 指向 context1。void bigData;const closureData = new Buffer(100);// 被閉包使用,創建 context: context2。// outClosure5 函數有 context1,previous 指向 context1。// 在 outClosure5 中新建的函數將綁定上context2。test.innerClosure5 = function () {      // 此函數閉包 context 指向 context2。  void innerData;
    }
    test.innerClosure5_1 = function () {      // 此函數閉包 context 指向 context2。  void closureData;
    }
  };
  test.outClosure5_1 = function () {

  }
  test.outClosure5();
}

run5(new Buffer(1000));

以上就是Node.js中出現內存泄漏的原因有哪些,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

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

AI

买车| 桦南县| 宜川县| 邳州市| 甘洛县| 渝北区| 新蔡县| 溧阳市| 大城县| 新宁县| 宁武县| 邵阳市| 新和县| 衡阳市| 西安市| 阳曲县| 麻江县| 礼泉县| 金平| 修水县| 改则县| 西乌珠穆沁旗| 堆龙德庆县| 区。| 丁青县| 彭山县| 沭阳县| 湘潭市| 壶关县| 峨山| 仁布县| 涡阳县| 彰武县| 宕昌县| 阳山县| 磴口县| 万山特区| 贵溪市| 天峻县| 江西省| 萨迦县|