NodeJS中的內存泄露

  內存泄露(Memory Leak)指因爲疏忽或錯誤形成程序未能釋放已經再也不使用內存的狀況。若是內存泄露的位置比較關鍵,那麼隨着處理的進行可能持有愈來愈多的無用內存,這些無用內存的變多會引發服務器響應速度變慢,嚴重的狀況下致使內存達到某個極限(多是進程的上限,如V8的上限;也多是系統可提供的內存上限)會使得應用程序奔潰。
  傳統的 C/C++ 中存在指針,對象在用完以後未釋放等狀況致使的內存泄露。而在使用虛擬機執行的語言中如 Java、Javascript 因爲使用了GC(Garbage Collection,垃圾回收)機制自動釋放內存,使得程序員的精力獲得了極大的解放,不用再像傳統語言那樣時刻對於內存的釋放而戰戰兢兢。
  可是,即便有了GC機制能夠自動釋放,但這並不意味着內存泄露的問題不存在了。內存泄露依舊是開發者們不能繞過的一個問題。javascript


GC in Node.js

  Node.js使用V8做爲Javascript的執行引擎,因此討論Node.js的GC狀況就等同於在討論V8的GC。在V8中一個對象的內存釋放被釋放,是看程序中是否還有地方持有該對象的引用。
  在V8中,每次GC時,是根據root對象(瀏覽器環境下的window,Node.js環境下的global)依次梳理對象的引用,若是能從root的引用鏈到達訪問,V8就會將其標記爲可到達對象,反之爲不可到達對象。被標記爲不可到達對象(即無引用的對象)後就會被V8回收。
  在NodeJS中內存泄露的緣由就是:本該被清除的對象,被可到達對象引用以後,未被正確的清除而常駐內存。java


內存泄露的幾種狀況

1、全局變量

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

這種緣由比較簡單,全局變量直接掛在 root 對象上,不會被清除掉。程序員


2、閉包

function out(){
    const data = Buffer.alloc(100);
    inner = function(){
        void data
    }
}
複製代碼

  閉包會引用到父級函數中的變量,若是閉包未釋放,就會致使內存泄露。
  這裏的例子只是簡單的將引用掛在全局對象上,實際的業務狀況可呢呢個是掛在某個能夠從root追溯到的對象上致使的。瀏覽器


3、事件監聽

  NodeJS的事件監聽也可能出現內存泄露。例如對同一個事件重複監聽,忘記移除(removeListener),將形成內存泄露。這種狀況很容易在複用對象上添加事件時出現。
  例如,NodeJS中Agent的keepAlive爲true時,可能形成內存的泄露。當Agent keepAlive爲true時,將會複用以前使用過的socket,若是在socket上添加事件監聽,忘記清除的話,由於socket的複用,將致使事件重複監遵從而致使內存泄露
  原理上與添加事件監聽忘了清除是同樣的。在使用NodeJS的http模塊時,不經過keepAlive複用是沒有問題的,複用了之後就可能產生內存泄露。因此你須要瞭解添加事件監聽對象的生命週期,並注意自行移除。緩存


其餘緣由

  還有一些其餘的狀況可能會致使內存泄露,好比緩存。在使用緩存的時候,得清楚緩存的對象是多少,若是緩存對象很是多,得作限制最大緩存數量處理。還有就是很是佔用CPU的代碼也會致使內存泄露,服務器在運行的時候,若是有高CPU的同步代碼,由於NodeJS是單線程的,因此就不能處理後面的請求了,請求堆積致使內存佔用太高。服務器


如何避免內存泄露

  • ESLint檢測代碼,排查非指望的全局變量。
  • 使用閉包的時候,得知道閉包了什麼對象,還有引用閉包的對象什麼時候清除閉包。最好能夠避免寫出複雜的閉包,由於複製的閉包引發的內存泄露,若是沒有打印內存快照的話,是很難看出來的。
  • 綁定事件的時候,必定得在恰當的時候清除事件。在編寫一個類的時候,推薦使用init函數對類的事件監聽進行綁定和資源申請,而後destory函數對事件和佔用資源進行釋放。
相關文章
相關標籤/搜索