Node中的內(nèi)存控制是什么

本文小編為大家詳細介紹“Node中的內(nèi)存控制是什么”,內(nèi)容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“Node中的內(nèi)存控制是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

創(chuàng)新互聯(lián)建站是一家集網(wǎng)站建設(shè),榆樹企業(yè)網(wǎng)站建設(shè),榆樹品牌網(wǎng)站建設(shè),網(wǎng)站定制,榆樹網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,榆樹網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。

1. V8的垃圾回收機制與內(nèi)存限制

Js由垃圾回收機制來進行自動內(nèi)存管理,開發(fā)者不需要像其它語言(c/c++)那樣在編寫代碼的過程中時刻關(guān)注內(nèi)存的分配和釋放問題。在瀏覽器中,垃圾回收機制對應(yīng)用程序構(gòu)成性能影響較小,但對于性能敏感的服務(wù)器端程序,內(nèi)存管理的好壞、垃圾回收狀況是否優(yōu)良,都會對服務(wù)構(gòu)成影響。

1.1 Node 與 V8

Node是一個構(gòu)建在Chrome的Js運行時上的平臺,V8為Node的Js腳本引擎

1.2 V8的內(nèi)存限制

在一般的后端語言中,對基本的內(nèi)存使用沒有什么限制,但是在Node中通過Js使用內(nèi)存時,只能使用部分內(nèi)存。在這樣的限制下,導(dǎo)致Node無法直接操作大內(nèi)存對象。

造成問題的主要原因在于Node基于V8構(gòu)建,在Node中使用的Js對象基本上都是通過V8自己的方式來進行分配和管理的。

1.3 V8的對象分配

在V8中,所有的Js對象都是通過堆來進行分配的。

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

Node中的內(nèi)存控制是什么

heapTotal 和 heapUsed是V8的堆內(nèi)存使用情況, 前者是已申請到的堆內(nèi)存,后者是當前使用的量。

在代碼中聲明變量并賦值時,所使用對象的內(nèi)存就分配在堆中。如果已申請的堆空閑內(nèi)存不夠分配新的對象,將繼續(xù)申請堆內(nèi)存,直到堆的大小超過V8的限制為止

V8為何要限制堆的大小,表層原因為V8最初為瀏覽器而設(shè)計,不太可能遇到用大量內(nèi)存的場景。對于網(wǎng)頁來說,V8的限制值已經(jīng)綽綽有余。深層原因是V8的垃圾回收機制的限制。按官方的說法,以1.5G的垃圾回收堆內(nèi)存為例,v8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。這是垃圾回收中引起Js線程暫停執(zhí)行的時間,應(yīng)用的性能和響應(yīng)能力都會直線下降。在當時的考慮下直接限制堆內(nèi)存是一個好的選擇。

這個限制是可以放開的,Node在啟動的時候,可以傳遞--max-old-space-size--max-new-space-size來調(diào)整內(nèi)存限制的大小,一旦生效就不能再動態(tài)改變。如:

node --max-old-space-size=1700 test.js // 單位為MB
// 或者
node --max-new-space-size=1024 test.js // 單位為KB

1.4 V8的垃圾回收機制

v8用到的各種垃圾回收算法

1.4.1 V8主要的垃圾回收算法

v8的垃圾回收策略主要基于分代式垃圾回收機制。

在實際的應(yīng)用中,對象的生存周期長短不一?,F(xiàn)在的垃圾回收算法中按對象的存活時間將內(nèi)存的垃圾回收進行不同的分代,對不同分代的內(nèi)存施以更高效的算法。

  • v8的內(nèi)存分代

    在v8中,主要將內(nèi)存分為新生代和老生代兩代。新生代中的對象為存活時間較短的對象,老生代中的對象為存活時間較長或常駐內(nèi)存的對象。

    新生代的內(nèi)存空間老生代的內(nèi)存空間

    v8堆的整體大小=新生代所用內(nèi)存空間+老生代的內(nèi)存空間

    v8堆內(nèi)存的最大值在64位系統(tǒng)下只能使用約1.4GB內(nèi)存和在32位系統(tǒng)下只能使用約0.7GB內(nèi)存

  • Scavenge算法

    在分代的基礎(chǔ)上,新生代中的對象主要通過Scavenge算法進行垃圾回收。Scavenge具體實現(xiàn)主要采用了Cheney算法。 Cheney算法是一種采用復(fù)制的方式實現(xiàn)的垃圾回收算法。將堆內(nèi)存一分為二,每一部分空間稱為semispace。在這兩個semispace空間中,只有一個處于使用中,另一個處于閑置狀態(tài)。處于使用狀態(tài)的semispace空間稱為From空間,處于閑置狀態(tài)的空間稱為To空間。分配對象時,先是在From空間進行分配。當開始進行垃圾回收時,會檢查From空間中的存活對象,這些存活對象將被復(fù)制到To空間中,而非存活對象占用的空間將會被釋放。完成復(fù)制后,F(xiàn)rom空間和To空間的角色發(fā)生對換。簡而言之,在垃圾回收過程中,就是通過將存活對象在兩個semispace空間之間進行復(fù)制。 Scavenge的缺點是只能使用堆內(nèi)存中的一半,這是由劃分空間和復(fù)制機制所決定的。但Scavenge由于只復(fù)制存活的對象,并且對于生命周期短的場景存活對象只占少部分,所以它在時間效率上有優(yōu)異的表現(xiàn)。 由于Scavenge是典型的犧牲空間換取時間的算法,所以無法大規(guī)模地應(yīng)用到所有的垃圾回收中。但Scavenge非常適合應(yīng)用在新生代中,因為新生代中對象的生命周期較短,適合這個算法。

    Node中的內(nèi)存控制是什么

    實際使用的堆內(nèi)存是新生代中的兩個semispace空間大小和老生代所用內(nèi)存大小之和。

當一個對象經(jīng)過多次復(fù)制依然存活時,會被認為是生命周期較長的對象,會被移動到老生代中,采用新的算法進行管理。對象從新生代中移動到老生代中的過程稱為晉升。

在單純的Scavenge過程中,F(xiàn)rom空間中的存活對象會被復(fù)制到To空間中去,然后對From空間和To空間進行角色對換(翻轉(zhuǎn))。但在分代式垃圾回收的前提下,F(xiàn)rom空間中的存活對象在復(fù)制到To空間之前需要進行檢查。在一定條件下,需要將存活周期長的對象移動到老生代中,也就是完成對象晉升。

對象晉升的條件主要有兩個,一個是對象是否經(jīng)歷過Scavenge回收,一個是To空間的內(nèi)存占用比超過限制。

在默認情況下,V8的對象分配主要集中在From空間中。對象從From空間中復(fù)制到To空間時,會檢查它的內(nèi)存地址來判斷這個對象是否已經(jīng)經(jīng)歷過一次Scavenge回收。如果已經(jīng)經(jīng)歷過了,會將該對象從From空間復(fù)制到老生代空間中,如果沒有,則復(fù)制到To空間中。晉升流程圖如下:

Node中的內(nèi)存控制是什么

另一個判斷條件是To空間的內(nèi)存占用比。從From空間復(fù)制一個對象到To空間時,如果To空間已經(jīng)使用了25%,則這個對象直接晉升到老生代空間中,晉升流程圖如下:

Node中的內(nèi)存控制是什么

設(shè)置25%限制的原因:當這次Scavenge回收完成后,這個To空間將變成From空間,接下來的內(nèi)存分配將在這個空間中進行。如果占比過高,會影響后續(xù)的內(nèi)存分配。

對象晉升后,將會在老生代空間中作為存活周期較長的對象來對待,接受新的回收算法處理。

  • Mark-Sweep & Mark-Compact

    對于老生代中的對象,由于存活對象占較大比重,如果采用Scavenge的方式有兩個問題:一個是存活對象較多,復(fù)制存活對象的效率將會很低;另一個問題是浪費一半的空間。為此,v8在老生代中主要采用了Mark-Sweep和Mark-Compact相結(jié)合的方式進行垃圾回收。

    Mark-Sweep是標記清除的意思,分為標記和清除兩個階段。與Scavenge相比,Mark-Sweep并不將內(nèi)存空間劃分為兩半,所以不存在浪費一半空間的行為。與Scavenge復(fù)制活著的對象不同,Mark-Sweep在標記階段遍歷堆中的所有對象,并標記活著的對象,在隨后的清除階段中,只清除沒有被標記的對象。可以發(fā)現(xiàn),Scavenge中只復(fù)制活著的對象,而Mark-Sweep只清理死亡的對象?;顚ο笤谛律兄徽驾^小部分,死對象在老生代中只占較小部分,這是兩種回收方式能高效處理的原因。Mark-Sweep在老生代空間中標記后的示意圖如下,黑色部分標記為死亡的對象

Node中的內(nèi)存控制是什么

Mark-Sweep最大的問題是在進行一次標記清除空間后,內(nèi)存空間會出現(xiàn)不連續(xù)的狀態(tài)。這種內(nèi)存碎片會對后續(xù)的內(nèi)存分配造成問題,需要分配一個大對象時,這時所有的碎片空間都無法完成此次分配,就會提前觸發(fā)垃圾回收,而這次回收是不必要的。

Mark-Compact就是為了解決Mark-Sweep的內(nèi)存碎片問題。Mark-Compact是標記整理的意思,是在Mark-Sweep的基礎(chǔ)上演變而來的。差別在于對象在標記為死亡后,在整理的過程中,將活著的對象往一端移動,移動完成后,直接清理掉邊界外的內(nèi)存。完成標記并移動存活對象后的示意圖,白色格子為存活對象,深色格子為死亡對象,淺色格子為存活對象移動后留下的空洞。

Node中的內(nèi)存控制是什么

完成移動后,就可以直接清除最右邊的存活對象后面的內(nèi)存區(qū)域完成回收。

在V8的回收策略中,Mark-Sweep 和 Mark-Compact 兩者是結(jié)合使用的。

3種主要垃圾回收算法的簡單對比

回收算法Mark-SweepMark-CompactScavenge
速度中等最慢最快
空間開銷少(有碎片)少(無碎片)雙倍空間(無碎片)
是否移動對象

由于Mark-Compact需要移動對象,所以它的執(zhí)行速度不可能很快,所以在取舍上,v8主要使用Mark-Sweep,在空間不足以對從新生代中晉升過來的對象進行分配時才使用Mark-Compact

  • Incremental Marking

    為了避免出現(xiàn)Js應(yīng)用邏輯與垃圾回收器看到的不一致情況,垃圾回收的3種基本算法都需要將應(yīng)用邏輯暫停下來,待執(zhí)行完垃圾回收后再恢復(fù)執(zhí)行應(yīng)用邏輯,這種行為稱為“全停頓”(stop-the-world).在v8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默認配置得較小,且其中存活對象通常較少,所以即便它是全停頓的影響也不大。但v8的老生代通常配置得較大,且存活對象較多,全堆垃圾回收(full垃圾回收)的標記、清理、整理等動作造成的停頓就會比較可怕,需要設(shè)法改善

    為了降低全堆垃圾回收帶來的停頓時間,v8先從標記階段入手,將原本要一口氣停頓完成的動作改為增量標記(incremental marking),也就是拆分為許多小“步進”,每做完一“步進”,就讓Js應(yīng)用邏輯執(zhí)行一小會兒,垃圾回收與應(yīng)用邏輯交替執(zhí)行直到標記階段完成。下圖為:增量標記示意圖

Node中的內(nèi)存控制是什么

v8在經(jīng)過增量標記的改進后,垃圾回收的最大停頓時間可以減少到原本的1/6左右。 v8后續(xù)還引入了延遲清理(lazy sweeping)與增量式整理(incremental compaction),讓清理與整理動作也變成增量式的。同時還計劃引入標記與并行清理,進一步利用多核性能降低每次停頓的時間。

1.5 查看垃圾回收的日志

在啟動時添加--trace_gc參數(shù)。在進行垃圾回收時,將會從標準輸出中打印垃圾回收的日志信息。

node --trace_gc -e "var a = [];for (var  i = 0; i < 1000000; i++) a.push(new Array(100));" > gc.log

Node中的內(nèi)存控制是什么

在Node啟動時使用--prof參數(shù),可以得到v8執(zhí)行時的性能分析數(shù)據(jù),包含了垃圾回收執(zhí)行時占用的時間。以下面的代碼為例

// test.js
for (var i = 0; i < 1000000; i++) {
    var a = {};
}

node --prof test.js

會生成一個v8.log日志文件

2. 高效使用內(nèi)存

如何讓垃圾回收機制更高效地工作

2.1 作用域(scope)

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

如下代碼:

var foo = function(){
    var local = {};
}

foo()函數(shù)在每次被調(diào)用時會創(chuàng)建對應(yīng)的作用域,函數(shù)執(zhí)行結(jié)束后,該作用域會被銷毀。同時作用域中聲明的局部變量分配在該作用域上,隨作用域的銷毀而銷毀。只被局部變量引用的對象存活周期較短。在這個示例中,由于對象非常小,將會被分配在新生代中的From空間中。在作用域釋放后,局部變量local失效,其引用的對象將會在下次垃圾回收時被釋放

2.1.1 標識符查找

標識符,可以理解為變量名。下面的代碼,執(zhí)行bar()函數(shù)時,將會遇到local變量

var bar = function(){
    console.log(local);
}

js執(zhí)行時會查找該變量定義在哪里。先查找的是當前作用域,如果在當前作用域無法找到該變量的聲明,會向上級的作用域里查找,直到查到為止。

2.1.2作用域鏈

在下面的代碼中

var foo = function(){
    var local = 'local var';
    var bar = function(){
        var local = 'another var';
        var baz = function(){
            console.log(local)
        };
        baz()
    }
    bar()
}
foo()

baz()函數(shù)中訪問local變量時,由于作用域中的變量列表中沒有l(wèi)ocal,所以會向上一個作用域中查找,接著會在bar()函數(shù)執(zhí)行得到的變量列表中找到了一個local變量的定義,于是使用它。盡管在再上一層的作用域中也存在local的定義,但是不會繼續(xù)查找了。如果查找一個不存在的變量,將會一直沿著作用域鏈查找到全局作用域,最后拋出未定義錯誤。

2.1.3 變量的主動釋放

如果變量是全局變量(不通過var聲明或定義在global變量上),由于全局作用域需要直到進程退出才能釋放,此時將導(dǎo)致引用的對象常駐內(nèi)存(常駐在老生代中)。如果需要釋放常駐內(nèi)存的對象,可以通過delete操作來刪除引用關(guān)系?;蛘邔⒆兞恐匦沦x值,讓舊的對象脫離引用關(guān)系。在接下來的老生代內(nèi)存清除和整理的過程中,會被回收釋放。示例代碼如下:

global.foo = "I am global object"
console.log(global.foo);// => "I am global object"
delete global.foo;
// 或者重新賦值
global.foo = undefined;
console.log(global.foo); // => undefined

雖然delete操作和重新賦值具有相同的效果,但是在V8中通過delete刪除對象的屬性有可能干擾v8的優(yōu)化,所以通過賦值方式解除引用更好。

2.2 閉包

作用域鏈上的對象訪問只能向上,外部無法向內(nèi)部訪問。

js實現(xiàn)外部作用域訪問內(nèi)部作用域中變量的方法叫做閉包。得益于高階函數(shù)的特性:函數(shù)可以作為參數(shù)或者返回值。

var foo = function(){
   var bar = function(){
       var local = "局部變量";
       return function(){
           return local;
       }
   }
   var baz = bar()
   console.log(baz())
}

在bar()函數(shù)執(zhí)行完成后,局部變量local將會隨著作用域的銷毀而被回收。但是這里返回值是一個匿名函數(shù),且這個函數(shù)中具備了訪問local的條件。雖然在后續(xù)的執(zhí)行中,在外部作用域中還是無法直接訪問local,但是若要訪問它,只要通過這個中間函數(shù)稍作周轉(zhuǎn)即可。

閉包是js的高級特性,利用它可以產(chǎn)生很多巧妙的效果。它的問題在于,一旦有變量引用這個中間函數(shù),這個中間函數(shù)將不會釋放,同時也會使原始的作用域不會得到釋放,作用域中產(chǎn)生的內(nèi)存占用也不會得到釋放。

無法立即回收的內(nèi)存有閉包和全局變量引用這兩種情況。由于v8的內(nèi)存限制,要注意此變量是否無限制地增加,會導(dǎo)致老生代中的對象增多。

3.內(nèi)存指標

會存在一些認為會回收但是卻沒有被回收的對象,會導(dǎo)致內(nèi)存占用無限增長。一旦增長達到v8的內(nèi)存限制,將會得到內(nèi)存溢出錯誤,進而導(dǎo)致進程退出。

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

process.memoryUsage()可以查看內(nèi)存使用情況。除此之外,os模塊中的totalmem()和freemem()方法也可以查看內(nèi)存使用情況

3.1.1 查看進程的內(nèi)存使用

調(diào)用process.memoryUsage()可以看到Node進程的內(nèi)存占用情況

Node中的內(nèi)存控制是什么

rss是resident set size的縮寫,即進程的常駐內(nèi)存部分。進程的內(nèi)存總共有幾部分,一部分是rss,其余部分在交換區(qū)(swap)或者文件系統(tǒng)(filesystem)中。

除了rss外,heapTotal和heapUsed對應(yīng)的是v8的堆內(nèi)存信息。heapTotal是堆中總共申請的內(nèi)存量,heapUsed表示目前堆中使用中的內(nèi)存量。單位都是字節(jié)。示例如下:

var showMem = function () {
  var mem = process.memoryUsage()
  var format = function (bytes) {
    return (bytes / 1024 / 1024).toFixed(2) + 'MB';
  }
  console.log('Process: heapTotal ' + format(mem.heapTotal) +
    ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss))
  console.log('---------------------')
}

var useMem = function () {
  var size = 50 * 1024 * 1024;
  var arr = new Array(size);
  for (var i = 0; i < size; i++) {
    arr[i] = 0
  }
  return arr
}

var total = []

for (var j = 0; j < 15; j++) {
  showMem();
  total.push(useMem())
}
showMem();

Node中的內(nèi)存控制是什么

在內(nèi)存達到最大限制值的時候,無法繼續(xù)分配內(nèi)存,然后進程內(nèi)存溢出了。

3.1.2 查看系統(tǒng)的內(nèi)存占用

os模塊中的totalmem()和freemem()這兩個方法用于查看操作系統(tǒng)的內(nèi)存使用情況,分別返回系統(tǒng)的總內(nèi)存和閑置內(nèi)存,以字節(jié)為單位

Node中的內(nèi)存控制是什么

3.2 堆外內(nèi)存

通過process.memoryUsage()的結(jié)果可以看到,堆中的內(nèi)存用量總是小于進程的常駐內(nèi)存用量,意味著Node中的內(nèi)存使用并非都是通過v8進行分配的。將那些不是通過v8分配的內(nèi)存稱為堆外內(nèi)存

將上面的代碼里的Array變?yōu)锽uffer,將size變大

var useMem = function () {
  var size = 200 * 1024 * 1024;
  var buffer = Buffer.alloc(size); // new Buffer(size)是舊語法
  for (var i = 0; i < size; i++) {
    buffer[i] = 0
  }
  return buffer
}

輸出結(jié)果如下:

Node中的內(nèi)存控制是什么

內(nèi)存沒有溢出,改造后的輸出結(jié)果中,heapTotal與heapUsed的變化極小,唯一變化的是rss的值,并且該值已經(jīng)遠遠超過v8的限制值。原因是Buffer對象不同于其它對象,它不經(jīng)過v8的內(nèi)存分配機制,所以也不會有堆內(nèi)存的大小限制。意味著利用堆外內(nèi)存可以突破內(nèi)存限制的問題

Node的內(nèi)存主要由通過v8進行分配的部分和Node自行分配的部分構(gòu)成。受v8的垃圾回收限制的只要是v8的堆內(nèi)存。

4. 內(nèi)存泄漏

Node對內(nèi)存泄漏十分敏感,內(nèi)存泄漏造成的堆積,垃圾回收過程中會耗費更多的時間進行對象掃描,應(yīng)用響應(yīng)緩慢,直到進程內(nèi)存溢出,應(yīng)用崩潰。

在v8的垃圾回收機制下,大部分情況是不會出現(xiàn)內(nèi)存泄漏的,但是內(nèi)存泄漏通常產(chǎn)生于無意間,排查困難。內(nèi)存泄漏的情況不盡相同,但本質(zhì)只有一個,那就是應(yīng)當回收的對象出現(xiàn)意外而沒有被回收,變成了常駐在老生代中的對象。通常原因有如下幾個:

  • 緩存

  • 隊列消費不及時

  • 作用域未釋放

4.1 慎將內(nèi)存當緩存用

緩存在應(yīng)用中的作用十分重要,可以十分有效地節(jié)省資源。因為它的訪問效率要比 I/O 的效率高,一旦命中緩存,就可以節(jié)省一次 I/O時間。

對象被當作緩存來使用,意味著將會常駐在老生代中。緩存中存儲的鍵越多,長期存活的對象也就越多,導(dǎo)致垃圾回收在進行掃描和整理時,對這些對象做無用功。

Js開發(fā)者喜歡用對象的鍵值對來緩存東西,但這與嚴格意義上的緩存又有著區(qū)別,嚴格意義的緩存有著完善的過期策略,而普通對象的鍵值對并沒有。是一種以內(nèi)存空間換CPU執(zhí)行時間。示例代碼如下:

var cache = {}; 
var get = function (key) { 
 if (cache[key]) { 
     return cache[key]; 
 } else { 
 // get from otherwise 
 } 
}; 
var set = function (key, value) { 
 cache[key] = value; 
};

所以在Node中,拿內(nèi)存當緩存的行為應(yīng)當被限制。當然,這種限制并不是不允許使用,而是要小心為之。

4.1.1 緩存限制策略

為了解決緩存中的對象永遠無法釋放的問題,需要加入一種策略來限制緩存的無限增長??梢詫崿F(xiàn)對鍵值數(shù)量的限制。下面是其實現(xiàn):

var LimitableMap = function (limit) {
  this.limit = limit || 10;
  this.map = {};
  this.keys = [];
};
var hasOwnProperty = Object.prototype.hasOwnProperty;
LimitableMap.prototype.set = function (key, value) {
  var map = this.map;
  var keys = this.keys;

  if (!hasOwnProperty.call(map, key)) {
    if (keys.length === this.limit) {
      var firstKey = keys.shift();
      delete map[firstKey];
    }
    keys.push(key);
  }
  map[key] = value;
};
LimitableMap.prototype.get = function (key) {
  return this.map[key];
};
module.exports = LimitableMap;

記錄鍵在數(shù)組中,一旦超過數(shù)量,就以先進先出的方式進行淘汰。

4.1.2 緩存的解決方案

直接將內(nèi)存作為緩存的方案要十分慎重。除了限制緩存的大小外,另外要考慮的事情是,進程之間無法共享內(nèi)存。如果在進程內(nèi)使用緩存,這些緩存不可避免地有重復(fù),對物理內(nèi)存的使用是一種浪費。

如何使用大量緩存,目前比較好的解決方案是采用進程外的緩存,進程自身不存儲狀態(tài)。外部的緩存軟件有著良好的緩存過期淘汰策略以及自有的內(nèi)存管理,不影響Node進程的性能。它的好處多多,在Node中主要可以解決以下兩個問題。

  • 將緩存轉(zhuǎn)移到外部,減少常駐內(nèi)存的對象的數(shù)量,讓垃圾回收更高效。

  • 進程之間可以共享緩存。

目前,市面上較好的緩存有redis和Memcached。

4.2 關(guān)注隊列狀態(tài)

隊列在消費者-生產(chǎn)者模型中經(jīng)常充當中間產(chǎn)物。這是一個容易忽略的情況,因為在大多數(shù)應(yīng)用場景下,消費的速度遠遠大于生產(chǎn)的速度,內(nèi)存泄漏不易產(chǎn)生。但是一旦消費速度低于生產(chǎn)速度,將會形成堆積, 導(dǎo)致Js中相關(guān)的作用域不會得到釋放,內(nèi)存占用不會回落,從而出現(xiàn)內(nèi)存泄漏。

解決方案應(yīng)該是監(jiān)控隊列的長度,一旦堆積,應(yīng)當通過監(jiān)控系統(tǒng)產(chǎn)生報警并通知相關(guān)人員。另一個解決方案是任意異步調(diào)用都應(yīng)該包含超時機制,一旦在限定的時間內(nèi)未完成響應(yīng),通過回調(diào)函數(shù)傳遞超時異常,使得任意異步調(diào)用的回調(diào)都具備可控的響應(yīng)時間,給消費速度一個下限值。

5. 內(nèi)存泄漏排查

常見的工具

  • v8-profiler: 可以用于對V8堆內(nèi)存抓取快照和對CPU進行分析

  • node-heapdump: 允許對V8堆內(nèi)存抓取快照,用于事后分析

  • node-mtrace: 使用了GCC的mtrace工具來分析堆的使用

  • dtrace:有完善的dtrace工具用來分析內(nèi)存泄漏

  • node-memwatch

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

由于Node的內(nèi)存限制,操作大文件也需要小心,好在Node提供了stream模塊用于處理大文件。

stream模塊是Node的原生模塊,直接引用即可。stream繼承自EventEmitter,具備基本的自定義事件功能,同時抽象出標準的事件和方法。它分可讀和可寫兩種。Node中的大多數(shù)模塊都有stream的應(yīng)用,比如fs的createReadStream()和createWriteStream()方法可以分別用于創(chuàng)建文件的可讀流和可寫流,process模塊中的stdin和stdout則分別是可讀流和可寫流的示例。

由于V8的內(nèi)存限制,我們無法通過fs.readFile()和fs.writeFile()直接進行大文件的操作,而改用fs.createReadStream()和fs.createWriteStream()方法通過流的方式實現(xiàn)對大文件的操作。下面的代碼展示了如何讀取一個文件,然后將數(shù)據(jù)寫入到另一個文件的過程:

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();
});

// 簡潔的方式
var reader = fs.createReadStream('in.txt'); 
var writer = fs.createWriteStream('out.txt'); 
reader.pipe(writer);

可讀流提供了管道方法pipe(),封裝了data事件和寫入操作。通過流的方式,上述代碼不會受到V8內(nèi)存限制的影響,有效地提高了程序的健壯性。

如果不需要進行字符串層面的操作,則不需要借助V8來處理,可以嘗試進行純粹的Buffer操作,這不會受到V8堆內(nèi)存的限制。但是這種大片使用內(nèi)存的情況依然要小心,即使V8不限制堆內(nèi)存的大小,物理內(nèi)存依然有限制。

讀到這里,這篇“Node中的內(nèi)存控制是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

新聞標題:Node中的內(nèi)存控制是什么
文章起源:http://www.muchs.cn/article36/ghoesg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、Google、網(wǎng)站導(dǎo)航搜索引擎優(yōu)化、全網(wǎng)營銷推廣

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化