node內(nèi)存控制

垃圾回收機制

  • nodejs在執(zhí)行JavaScript時,內(nèi)存受到v8限制,64位約為1.4g,32位0.7g

  • 所有js對象是通過堆分配,查看process.memoryUsage()

  • 限制內(nèi)存原因:垃圾回收時,js線程會暫停執(zhí)行(避免JS應(yīng)用邏輯與垃圾回收器看到的不一樣),大量的堆內(nèi)存回收嚴重影響性能

  • v8內(nèi)存整體包含新生代和老生代

      // 調(diào)整內(nèi)存限制的大小
      node --max-old-space-size=1700 test.js // 單位為MB 
      node --max-new-space-size=1024 test.js // 單位為KB
    
      在V8初始化時生效,一旦生效不能動態(tài)變化  
    

新生代

  • 由兩個reserved_semispace_size_(32位16mb,62位32mb)構(gòu)成
  • 通過Scavenge算法進行回收,具體實現(xiàn)采用Cheney算法

Cheney算法采用復(fù)制方式實現(xiàn)垃圾回收,將堆內(nèi)存分成2塊,一個使用(From),一個空閑(To).分配對象在From空間。開始垃圾回收時,檢查From里的存活對象,并將它們復(fù)制到To,非存活對象占用的空間會被釋放,然后From、to角色對換。將存活對象在兩個空間之間復(fù)制

優(yōu)點是時間短、缺點是只能使用一半堆內(nèi)存。新生代對象生命周期短,適合此算法

當對象經(jīng)過多次復(fù)制依然存活,就會晉升到老生代。對像晉升的條件,是否經(jīng)歷過Scavenge回收,To空間內(nèi)存占用比超過限制

老生代

  • 在64未系統(tǒng)下為1400 MB,在32為700 MB
  • 使用Mark-Sweep和Mark-Compact進行垃圾回收

Mark-Sweep 標記清除,先遍歷堆的對象,標記活的對象,之后清除沒標記的對象。死對象在老生代只占少部分,所以高效

Mark-Compact 整理內(nèi)存,將Mark-Sweep清理后散開的對象移動到一段。

v8主要使用Mark-Sweep,在空間不足以對新晉升對象分配時才用Mark-Compact

增量標記(incremental marking)

  • 降低老生代的全堆垃圾回收帶來的時間停頓
  • 從標記階段入手,拆分為許多小步進,與應(yīng)用邏輯交替運行
  • 垃圾回收最大停頓時間降為原來的1/6

垃圾回收是影響性能的因素之一,要盡量減少垃圾回收,尤其全堆垃圾回收

查看垃圾回收日志

  • 在啟動時添加--trace_gc

啟動時使用--prof,可以得到v8性能分析數(shù)據(jù),包含垃圾回收占用的時間,需要使用工具讀取,在Node源碼的deps/v8/tools,linux-tick-processor

高效使用內(nèi)存

作用域

  • js中能形成作用域的有函數(shù)調(diào)用、with和全局作用域

例如,在函數(shù)調(diào)用時,會創(chuàng)建對應(yīng)的作用域,在執(zhí)行結(jié)束后銷毀,并且在該作用域申明的局部變量也會被銷毀

  1. 標識符查找(即變量名) 先查找當前作用域,再向上級作用域,一直到全局作用域
  2. 變量主動釋放 全局變量要直到進程退出才釋放,導(dǎo)致引用對象常駐老生代,可以用delete刪除或者賦undefined、null(delete刪除對象的屬性可能干擾v8,所以賦值更好)

閉包

  • 外部作用域訪問內(nèi)部作用域的方法,得益于高階函數(shù)特性
var foo = function() {
    var bar = function() {
        var local = "局部變量";
        return function() {
            return local;
        };
    };
    var baz = bar();
    console.log(baz());
};

bar()返回一個匿名函數(shù),一旦 有變量引用它,它的作用域?qū)⒉粫尫?,直到?jīng)]有引用

把閉包賦值給一個不可控的對象時,會導(dǎo)致內(nèi)存泄漏。使用完,將變量賦其他值或置空

查看內(nèi)存使用情況

  • 查看進程內(nèi)存占用 process.memoryUsage(),其中rss為進程的常駐內(nèi)存(node所占的內(nèi)存),heapTotal、heapUsed為堆內(nèi)存使用情況
  • os.totalmem(),os.freemem() 查看系統(tǒng)內(nèi)存

Node使用的內(nèi)存不是都通過v8分配,還有堆外內(nèi)存,用于處理網(wǎng)絡(luò)流、I/O流

內(nèi)存泄漏

造成的原因:緩存、隊列消費不及時、作用域未釋放

緩存

  • 限制內(nèi)存當緩存,要限制好大小,做好釋放
  • 進程之間不能共享內(nèi)存,所以用內(nèi)存做緩存也是

為了加速模塊引入,模塊會在編譯后緩存,由于通過exports導(dǎo)出(閉包),作用域不會釋放,常駐老生代。要注意內(nèi)存泄漏

var leakArray = [];
exports.leak = function() {
    leakArray.push("leak" + Math.random());
};
//局部變量leakArray不停增加內(nèi)存占用,且不會釋放,如果必須如此設(shè)計,要提供釋放接口

推薦使用進程外緩存,<a target='_blank'>Redis</a>、<a target='_blank'>Memcached</a>

隊列狀態(tài)

  • 在生產(chǎn)者和消費者中間
  • 監(jiān)控隊列的長度,超過長度就拒絕
  • 任意的異步調(diào)用應(yīng)該包含超時機制

內(nèi)存泄漏排查

node-heapdump
  1. 安裝 npm install heapdump
  2. 在開頭引入 var heapdump = require('heapdump');
  3. 發(fā)送命令kill -USR2 <pid>,heapdump會抓拍一份堆內(nèi)存快照,文件為heapdump-<sec>.<usec>.heapsnapshot格式,是json文件
node-memwatch
var memwatch = require('memwatch');
memwatch.on('leak', function(info) {
    console.log('leak:');
    console.log(info);
});
memwatch.on('stats', function(stats) {
    console.log('stats:') console.log(stats);
});

在進程使用node-memwatch后,每次全堆垃圾回收,會觸發(fā)stats事件,該事件會傳遞內(nèi)存的統(tǒng)計信息

stats: {
        num_full_gc: 4, //   第幾次全堆垃圾回收
        num_inc_gc: 23, //    第幾次增量垃圾回收 
        heap_compactions: 4, //  第幾次對老生代整理
        usage_trend: 0, // 使用趨勢
        estimated_base: 7152944, // 預(yù)估基數(shù) 
        current_base: 7152944, // 當前基數(shù)
        min: 6720776, //  最小
        max: 7152944  //最大
    } 

如果經(jīng)過連續(xù)的5次垃圾回收后,內(nèi)存仍沒有被釋放,意味有內(nèi)存泄漏,node-memwatch會觸發(fā)leak事件

leak: 8 {
    start: Mon Oct 07 2013 13: 46: 27 GMT + 0800(CST),
    end: Mon Oct 07 2013 13: 54: 40 GMT + 0800(CST),
    growth: 6222576,
    reason: 'heap growth over 5 consecutive GCs (8m 13s) - 43.33 mb/hr'
}
//顯示內(nèi)存增長了多了

使用node-memwatch的抓取快照和比較快照,能將內(nèi)存泄漏定位到v8的堆上

var memwatch = require('memwatch');
var leakArray = [];
var leak = function() {
    leakArray.push("leak" + Math.random());
};
// Take first snapshot
var hd = new memwatch.HeapDiff();
for (var i = 0; i < 10000; i++) {
    leak();
}
// Take the second snapshot and compute the diff 
var diff = hd.end();
console.log(JSON.stringify(diff, null, 2));

{
    "before": {
        "nodes": 11719,
        "time": "2013-10-07T06:32:07.000Z",
        "size_bytes": 1493304,
        "size": "1.42 mb"
    },
    "after": {
        "nodes": 31618,
        "time": "2013-10-07T06:32:07.000Z",
        "size_bytes": 2684864,
        "size": "2.56 mb"
    },
    "change": {
        "size_bytes": 1191560,
        "size": "1.14 mb",
        "freed_nodes": 129, //釋放的節(jié)點數(shù)
        "allocated_nodes": 20028,//分配的節(jié)點數(shù)
        "details": [{
            "what": "Array",
            "size_bytes": 323720,
            "size": "316.13 kb",
            "+": 15,
            "-": 65
        }, {
            "-": 28
        }, {
            "what": "String",
            "size_bytes": 879424,
            "what": "Code",
            "size_bytes": -10944,
            "size": "-10.69 kb",
            "+": 8,
            "size": "858.81 kb",
            "+": 20001, //大量的string未被回收
            "-": 1
        }]
    }
}

大內(nèi)存應(yīng)用

  • 使用stream模塊處理大文件,fs的createReadStream(),createWriteStream()
var reader = fs.createReadStream('in.txt');
var writer = fs.createWriteStream('out.txt');
reader.on('data', function(chunk) {
    writer.write(chunk);
});
reader.on('end', function() {
    writer.end();
});

//管道方法pipe(),封裝了data事件和寫入
var reader = fs.createReadStream('in.txt');
var writer = fs.createWriteStream('out.txt');
reader.pipe(writer);

在不需要進行字符串操作時,可以不借助v8,使用Buffer操作,這樣不會受到v8的內(nèi)存限制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JS在瀏覽器中運行的時候并不存在太大的內(nèi)存問題,我們通常也不刻意的去優(yōu)化他們,但是當運行在服務(wù)器端的時候,運行時間...
    exialym閱讀 2,148評論 0 6
  • 使用JavaScript進行前端開發(fā)時幾乎完全不需要關(guān)心內(nèi)存管理問題,對于前端編程來說,V8限制的內(nèi)存幾乎不會出現(xiàn)...
    寫B(tài)log不取名閱讀 11,181評論 9 20
  • 早早就列了出行清單: 護膝,手臺,帽子,圍巾,套袖,拖鞋,短褲,手套,手帳。充電寶,眼鏡,雨傘,自拍桿。脈動1升,...
    大新子6閱讀 642評論 16 9
  • 很多人都說想多賺錢、想發(fā)財,但如果真正深究下去,其實對財富有強烈渴望的人是很少的,絕大多數(shù)人只是說說而已,而不是內(nèi)...
    文會閱讀 517評論 0 0

友情鏈接更多精彩內(nèi)容