2012-12-12 61 views
19

我對使用try/catch塊來捕獲OutOfMemoryException的事實有點困惑。捕捉OutOfMemoryException如何工作?

鑑於以下代碼:

Console.WriteLine("Starting"); 

for (int i = 0; i < 10; i++) 
{ 
    try 
    { 
     OutOfMemory(); 
    } 
    catch (Exception exception) 
    { 
     Console.WriteLine(exception.ToString()); 
    } 
} 

try 
{ 
    StackOverflow(); 
} 
catch (Exception exception) 
{ 
    Console.WriteLine(exception.ToString()); 
} 

Console.WriteLine("Done"); 

我用於創建內存不足+ StackOverflowException的方法:

public static void OutOfMemory() 
{ 
    List<byte[]> data = new List<byte[]>(1500); 

    while (true) 
    { 
     byte[] buffer = new byte[int.MaxValue/2]; 

     for (int i = 0; i < buffer.Length; i++) 
     { 
      buffer[i] = 255; 
     } 

     data.Add(buffer); 
    } 
} 

static void StackOverflow() 
{ 
    StackOverflow(); 
} 

它打印出OutOfMemoryException 10次,然後終止由於StackOverflowException,這它無法處理。

的RAM圖形看起來就像是在執行程序: graph showing that memory gets allocated and released 10 times

我現在的問題是,爲什麼我們能夠趕上OutOfMemoryException?捕獲後,我們可以繼續執行我們想要的任何代碼。正如RAM圖所證明的那樣,已經釋放了內存。運行時如何知道哪些對象可以GC以及哪些對象仍然需要進一步執行?

+0

向我們展示您的OutOfMemory()方法。我敢打賭,你沒有保留它分配的內存的引用,所以GC可以在它返回之後釋放它。 –

+0

完成,你是對的 – GameScripting

+0

是的,所以只要OutOfMemory()拋出,它分配的所有內存都可以被GC釋放。 –

回答

24

GC對程序中使用的引用進行了分析,並且可以丟棄任何不在任何地方使用的對象。

OutOfMemoryException並不意味着內存已經完全耗盡,而只是意味着內存分配失敗。如果您嘗試一次分配大內存區域,則可能仍有大量可用內存。

當沒有足夠的可用內存用於分配時,系統會執行垃圾回收以嘗試釋放內存。如果分配的內存不足,則會拋出異常。

A StackOverflowException是不可能處理的,因爲這意味着堆棧已滿,並且無法像堆一樣從堆中除去任何東西。您將需要更多堆棧空間來繼續運行處理異常的代碼,但沒有更多。

5

OutOfMemoryException很可能是因爲你正在運行一個32位程序而引起的,因爲你沒有指出內存圖表顯示系統有多少內存,所以也許嘗試將它構建爲64位,並且可能使用MemoryFailPoint來防止無論如何這發生。

您也可以讓我們知道OutOfMemory()函數中的內容,以獲得更清晰的圖片。

P.S. StackOverFlow是唯一無法處理的錯誤。

編輯:如上所述,我認爲這只是合乎邏輯的,因此之前沒有提及它,例如,如果您嘗試分配更多內存而不是「備用」,則不可能這樣做,發生異常。由於您正在爲您的data.Add()分配大型數組,所以在最終的「非法」添加發生之前它會崩潰,因此仍然存在空閒內存。

所以我會假設它在這一點data.Add(緩衝區);在通過將「400MB」字節數組添加到「數據」時,在跳過2GB的進程限制時,該問題在數組構建期間發生。一個大約10億個對象的數組,每個字節4個字節,我希望大約400MB。

P.S.直到.net 4.5最大進程內存分配爲2GB,之後4.5可用。

+0

啊,所以它的內存不足,這導致我相信你正在達到應用程序限制,而不一定是系統限制。 –

+0

我對如何解決這個問題不感興趣,沒有實際問題,只關心研究和理解CLR是如何工作的 – GameScripting

+0

P.S. StackOverflow不是唯一一個你無法捕捉的。你也有'AccessViolationException'。 –

0

不知道這是否回答你的問題,但它是如何決定的(簡化)解釋哪些對象進行清理是這樣的:

垃圾收集器利用每一個正在運行的線程在你的程序,並標記所有頂級對象,這意味着所有可以從堆棧幀訪問的對象(即,當前執行點處的局部變量指向的所有對象)以及靜態字段指向的所有對象。

然後,它會標記下一級對象,這意味着所有先前標記的對象的所有對象指向的對象。重複該步驟直到沒有新的對象被標記。

由於C#不允許在正常上下文中使用指針,因此一旦完成上一步,就可以保證未標記的對象不會被後續代碼訪問,因此可以安全地進行清理。在你的情況下,如果你分配給內存管理器增加壓力的對象沒有被引用保存,這意味着GC將有機會清除它們。另外,請記住,OutOfMemoryException指的是CLR程序的託管內存,而GC則在該「盒子」之外工作。

0

你能趕上OutOfMemoryException的原因是因爲語言設計者決定讓你。有時(但通常不)實際的原因是因爲在某些情況下它是可恢復的情況。

如果您嘗試分配一個巨大的數組,您可能會得到一個OutOfMemoryException,但是該巨大數組的內存實際上並未被分配 - 所以其他代碼仍然可以毫無問題地運行。此外,由於異常導致的堆棧展開可能導致其他對象有資格進行垃圾回收,從而進一步增加可用內存量。

0

您的OutOfMemory()方法會創建方法範圍本地的數據結構(List<byte[]>)。當您的執行線程位於OutOfMemory方法內時,當前堆棧幀將被視爲List的GC根目錄。一旦你的線程在catch塊中結束,堆棧框架就會彈出並且列表實際上變得無法訪問。因此,垃圾收集器確定它可以安全地收集列表(它在您的內存圖中觀察到)。