2013-11-21 60 views
5

大量JavaScript對象,我是新手,這樣的JavaScript的,所以我會給出一個簡要說明:刪除當進程運行內存

我有一個Web刮板內置Nodejs它收集(相當一點點)數據,用Cheerio(基本上jQueryNode)處理它創建一個對象,然後將其上傳到mongoDB。

它工作得很好,除了在較大的網站上。什麼是出現要發生的事情是:

  1. 我給刮板在線商店的網址刮
  2. 節點轉到該網址,隨時隨地獲取從5000 - 40000產品網址刮
  3. 對於每這些新的URL,Node的request模塊獲取頁面源,然後將數據加載到Cheerio
  4. 使用Cheerio我創建了一個代表產品的JS對象。
  5. 我將對象發送到MongoDB,並保存到我的數據庫。

正如我所說,這種情況發生在成千上萬的URL上,一旦我得到,比如說,加載了10,000個網址,我就會在節點中看到錯誤。最常見的是:

Node: Fatal JS Error: Process out of memory 

好吧,這裏的實際問題(S):

認爲這種情況正在發生,因爲節點的垃圾清理工作不正常。例如,有可能從所有40,000個URL中獲取的數據仍在內存中,或者至少有40,000個創建的JavaScript對象可能存在。也許這也是因爲MongoDB連接是在會話開始時進行的,並且從不關閉(我只是在完成所有產品後手動關閉腳本)。這是爲了避免每次登錄新產品時打開/關閉連接。

要真正確保它們被正確地清理(一旦產品進入MongoDB,我不再使用它並且可以從內存中刪除),我可以/只是簡單地使用delete product從內存中刪除它?如果我刪除對象的一個​​引用是完全從內存中刪除的,還是我必須刪除所有這些對象?(我明顯不知道JS如何處理對象)?

例如:

var saveToDB = require ('./mongoDBFunction.js'); 

function getData(link){ 
    request(link, function(data){ 
     var $ = cheerio.load(data); 
     createProduct($) 
    }) 
} 

function createProduct($) 
    var product = { 
     a: 'asadf', 
     b: 'asdfsd' 
     // there's about 50 lines of data in here in the real products but this is for brevity 
    }  
    product.name = $('.selector').dostuffwithitinjquery('etc'); 
    saveToDB(product); 
} 

// In mongoDBFunction.js 

exports.saveToDB(item){ 
    db.products.save(item, function(err){ 
     console.log("Item was successfully saved!"); 
     delete item; // Will this completely delete the item from memory? 
    }) 
} 

回答

9

delete在JavaScript並不是用來刪除變量或釋放內存。它僅用於從對象中移除屬性。您可能會發現this articledelete運營商有很好的閱讀。

通過將變量設置爲null之類的東西,您可以刪除對變量中保存的數據的引用。如果沒有其他對該數據的引用,那麼這將使其有資格進行垃圾收集。如果還有其他對該對象的引用,那麼它將不會從內存中清除,直到沒有更多引用(例如,代碼無法訪問它)。

至於什麼導致內存積累,有很多的可能性,我們不能真正看到足夠的代碼來知道什麼引用可以保持,這將阻止GC釋放事物。

如果這是一個沒有執行中斷的長時間運行的單個進程,您可能還需要手動運行垃圾回收器,以確保它有機會清理已發佈的內容。

以下是一些關於在node.js中跟蹤內存使用情況的文章:http://dtrace.org/blogs/bmc/2012/05/05/debugging-node-js-memory-leaks/https://hacks.mozilla.org/2012/11/tracking-down-memory-leaks-in-node-js-a-node-js-holiday-season/

+0

有趣。所以在上面的例子中,我需要''mongoDBFunction.js'中的變量以及主腳本發送到'saveToDB()'函數後'null?這是讓我困惑的對象的傳遞,試圖找出實際上有多少數據的「副本」。 – Jascination

+1

只要你沒有在數組或類似的東西中累積數據引用,你通常不需要'null'。垃圾收集器(當給定週期運行時)將處理事情。除非DB或'cheerio'庫在內存中緩存或存儲內容,或者除非您將所有數據累積到自己的數組中,否則我猜測您可能需要手動運行GC。我會首先嚐試手動運行GC。如果這不起作用,那麼使用其中一個工具來顯示哪些對象正在使用內存。 – jfriend00

+1

@Jascination - 還要記住,JavaScript通過引用傳遞數據(如數組和對象)和字符串(它不會因爲傳遞而傳遞它們的新副本)。 – jfriend00

3

JavaScript有一個垃圾回收器,可以自動跟蹤哪個變量是「可到達的」。如果一個變量是「可達」的,那麼它的值不會被釋放。

例如,如果你有一個全局變量var g_hugeArray並且給它分配了一個巨大的數組,你實際上在這裏有兩個JavaScript對象:一個是保存數組數據的巨大塊。另一個是窗口對象的名稱爲「g_hugeArray」的屬性,指向該數據。所以引用鏈是:window - > g_hugeArray - >實際的數組。

爲了釋放實際的數組,您將實際的數組設置爲「無法訪問」。你可以打破或者鏈接上面的鏈條來實現這一點。如果將g_hugeArray設置爲null,那麼可以中斷g_hugeArray和實際數組之間的鏈接。這使得數組數據無法訪問,因此它將在垃圾收集器運行時被釋放。或者,您可以使用「delete window.g_hugeArray」從窗口對象中刪除屬性「g_hugeArray」。這打破了窗口和g_hugeArray之間的鏈接,也使得實際的數組無法訪問。

當你有「關閉」時,情況變得更加複雜。當您有一個引用局部變量的本地函數時,會創建閉包。例如:

function a() 
{ 
    var x = 10; 
    var y = 20; 
    setTimeout(function() 
     { 
      alert(x); 
     }, 100); 
} 

在這種情況下,局部變量x仍然是來自匿名超時功能,即使功能「一」返回到達。如果沒有超時函數,那麼只要函數a返回,局部變量x和y都將無法訪問。但匿名函數的存在改變了這一點。根據JavaScript引擎的實現方式,它可以選擇保留變量x和y(因爲它不知道函數是否需要y直到函數實際運行,這發生在函數a返回後)。或者如果它足夠聰明,它只能保持x。想象一下,如果x和y都指向大事情,這可能是一個問題。因此,關閉非常方便,但有時更容易導致內存問題,並且可能使跟蹤內存問題變得更加困難。

1

我在我的應用程序中遇到了類似功能的問題。我一直在尋找內存泄漏或類似的東西。我的進程佔用的內存大小已達到1.4 GB,取決於必須下載的鏈接數量。

我注意到的第一件事是手動運行垃圾收集器後,幾乎所有的內存都被釋放了。我下載的每個頁面大約需要1 MB,已處理並存儲在數據庫中。

然後我安裝heapdump並查看應用程序的快照。有關內存分析的更多信息,請參閱Webstorm Blog

enter image description here

我的猜測是,應用程序運行時,GC無法啓動。爲此,我開始運行標誌爲--expose-gc的應用程序,並在程序實施時開始手動運行GC。

const runGCIfNeeded = (() => { 
    let i = 0; 
    return function runGCIfNeeded() { 
     if (i++ > 200) { 
      i = 0; 

      if (global.gc) { 
       global.gc(); 
      } else { 
       logger.warn('Garbage collection unavailable. Pass --expose-gc when launching node to enable forced garbage collection.'); 
      } 
     } 
    }; 
})(); 

// run GC check after each iteration 
checkProduct(product._id) 
    .then(/* ... */) 
    .finally(runGCIfNeeded)