.Net 垃圾回收機制原理(二)

英文原文:Jeffrey Richterhtml

編譯:趙玉開c++

連接http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html
程序員

上一篇文章介紹了.Net 垃圾回收的基本原理和垃圾回收執行Finalize方法的內部機制;這一篇咱們看下弱引用對象,代,多線程垃圾回收,大對象處理以及和垃圾回收相關的性能計數器。
讓咱們從弱引用對象提及,弱引用對象能夠減輕大對象帶來的內存壓力。
弱引用(Weak References)
當程序的根對象指向一個對象時,這個對象是可達的,垃圾回收器不能回收它,這稱爲對對象的強引用。和強引用相對的是弱引用,當一個對象上存在弱引用時,垃圾回收器能夠回收此對象,可是也容許程序訪問這個對象。這是怎麼回事兒呢?請往下看。
算法

若是一個對象上僅存在弱引用,而且垃圾回收器在運行,這個對象就會被回收,以後若是程序中要訪問這個對象,訪問就會失敗。另外一方面,要使用弱引用的對象,程序必須先對這個對象進行強引用,若是程序在垃圾回收器回收這個對象以前對對象進行了強引用,這樣(有了強引用以後)垃圾回收器就不能回收此對象了。這有點繞,讓咱們用一段代碼來講明一下:數組

[csharp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. void Method() {  
  2. //建立對象的強引用  
  3. Object o = new Object();   
  4. // 用一個短弱引用對象弱引用o.  
  5. WeakReference wr = new WeakReference(o);  
  6.   
  7. o = null; // 移除對象的強引用  
  8.   
  9. o = wr.Target; //嘗試從弱引用對象中得到對象的強引用  
  10. if (o == null) {  
  11. // 若是對象爲空說明對象已經被垃圾回收器回收掉了  
  12. else {  
  13. // 若是垃圾回收器尚未回收此對象就能夠繼續使用對象了  
  14. }  
  15. }  

爲何須要弱對象呢?由於,有一些數據建立起來很容易,可是卻須要不少內存。例如:你有一個程序,這個程序須要訪問用戶硬盤上的全部文件夾和文件名;你能夠在程序第一次須要這個數據時訪問用戶磁盤生成一次數據,數據生成以後你就能夠訪問內存中的數據來獲得用戶文件數據,而不是每次都去讀磁盤得到數據,這樣作能夠提高程序的性能。緩存

問題是這個數據可能至關大,須要至關大的內存。若是用戶去操做程序的另一部分功能了,這塊至關大的內存就沒有佔用的必要了。你能夠經過代碼刪除這些數據,可是若是用戶立刻切換到須要這塊數據的功能上,你就必須從新從用戶的磁盤上構建這個數據。弱引用爲這種場景提供了一種簡單有效的方案。
當用戶切換到其餘功能時,你能夠爲這個數據建立一個弱引用對象,並把對這個數據的強引用解除掉。這樣若是程序佔用的內存很低,垃圾回收操做就不會觸發,弱引用對象就不會被回收掉;這樣當程序須要使用這塊數據時就能夠經過一個強引用來得到數據,若是成功獲得了對象引用,程序就沒有必要再次讀取用戶的磁盤了。
安全

WeakReference類型提供了兩個構造函數:性能優化

 

[csharp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. WeakReference(object target);  
  2. WeakReference(object target, bool trackResurrection);  

 

target參數顯然就是弱引用要跟蹤的對象了。trackResurrection參數表示當對象的Finalize方法執行以後是否還要跟蹤這個對象。默認這個參數是false。有關對象的復活請參考這裏。服務器

方便起見,不跟蹤復活對象的弱引用稱爲「短弱引用」;而要跟蹤復活對象的的弱引用稱爲「長弱引用」。若是對象沒有實現Finalize方法,那麼長弱引用和短弱引用是徹底同樣的。強烈建議你儘可能避免使用長弱引用。長弱引用容許你使用復活的對象,而復活對象的行爲多是不能夠預知的。
一旦你使用WeakReference引用了一個對象,建議你將這個對象的全部強用都設置爲null;若是強引用存在的話,垃圾回收器是永遠都不可能回收弱引用指向的對象的。
當你要使用弱引用目標對象時,你必須爲目標對象建立一個強引用,這很簡單,只要用object a = weekRefer.Target;就能夠了,而後你必須判斷a是否爲空,弱不爲空才能夠繼續使用,弱爲空就表示對象已經被垃圾回收器回收了,得經過其餘方法從新得到此對象。
弱引用的內部實現
從前文中的描述中咱們能夠推斷出弱引用對象確定和通常對象的處理是不同的。通常狀況下若是一個對象引用了另外一個對象就是強引用,垃圾回收器就不能回收被引用的對象,而WeakReference對象卻不是這樣子,它引用的對象是有可能被回收的。
要徹底理解弱對象是如何工做的,咱們還須要看一下託管堆。託管堆上有兩個內部數據結構他們的惟一做用是管理弱引用:咱們能夠把它們稱做長弱引用表和短弱引用表;這兩個表存放託管堆上的弱引用目標對象指針。
程序運行之初,這兩個表都是空的。當你建立一個WeakReference對象時,這個對象並非分配到託管堆上的,而是在弱對象表中建立一個空槽(Empty Slot)。短弱引用對象被放在短弱對象表中,長弱引用對象被放在長弱引用表中。
一旦發現空槽,空槽的值會被設置成弱引用目標對象的地址;顯然長短弱對象表中的對象是不會看成應用程序的根對象的。垃圾回收器不會回收長短弱對象表中的數據。
讓咱們來看下垃圾回收執行時發生了什麼:
1. 垃圾回收器構建一個可達對象圖,構建步驟請參考上文
2. 垃圾回收器掃描短弱對象表,若是弱對象表中指向的對像沒有在可達對象圖中,那麼這個對像就被標識爲垃圾對象,而後短對象表中的對象指針被設置爲空
3. 垃圾回收器掃描終結隊列(參考上文),若是隊列中的對象不在可達對象圖中,這個對象從終結隊列中移動到Freachable隊列中,這時候,這個對象又被標識爲可達對象,再也不是垃圾了
4. 垃圾回收器掃描長弱引用表。若是表中的對象不在可達對象圖中(可達對象圖中包括在Freachable隊列中對象),將長引用對象表中對應的對象指針設置爲null
5. 垃圾回收器移動可達對象
一旦你理解了垃圾回收器的工做過程,就很容易理解弱引用是如何起做用了。訪問WeakReference的Target屬性致使系統返回弱對象表中的目標對象指針,若是是null,表示對象已經被回收了。
短弱引用不跟蹤復活,這意味着垃圾回收器能夠在掃描終結隊列以前檢查弱引用表中指向的對象是不是垃圾對象。
而長弱引用跟蹤復活對象,這意味着垃圾回收器必須在確認對象回收以後才能夠將弱引用表中的指針設置爲null。
代:
提起.Net的垃圾回收,c++或者c程序員可能就會想,這麼管理內存會不會出現性能問題呢。GC的開發人員一直在調整垃圾回收器提高它的性能。代就是一種爲了下降垃圾回收對性能影響的機制。垃圾回收器在工做時會假定以下說法是成立的:
1. 一個對象越新,那麼這個對象的生命週期就越短
2. 一個對象越老,那麼這個對象的生命週期就越長
3. 新對象之間一般更可能和新對象之間存在引用關係
4. 壓縮堆的一部分要比壓縮整個堆要快
固然大量研究證實以上幾個假設在不少程序上是成立的。那就讓咱們來談談這幾個假設是如何影響垃圾回收器工做的吧。
在程序初始化時,託管堆上沒有對象。這時候新添到託管堆上的對象是的代是0.以下圖所示,0代對象是最年輕的對象,他們歷來沒有通過垃圾回收器的檢查。數據結構


 

圖1 託管堆上的0代對象

如今若是堆上添加了更多的對象,堆填滿時就會觸發垃圾回收。當垃圾回收器分析託管堆時,會構建一個垃圾對象(圖2中淺紫色塊)和非垃圾對象的圖。全部沒有被回收的對象會被移動壓縮到堆的最底端。這些沒有被回收掉的對象就成爲了1代對象,如圖2所示


 

圖2 託管堆上的0代1代對象

當堆上分配了更多的對象時,新對象被放在了0代區。若是0代堆填滿了,就會觸發一次垃圾回收。這時候活下來的對象成爲1代對象被移動到堆的底部;再此發生垃圾回收後1代對象中存活下來的對象會提高爲2代對象並被移動壓縮。如圖3所示:


 

圖3 託管堆上的0、一、2代對象

2代對象是目前垃圾回收器的最高代,當再次垃圾回收時,沒有回收的對象的代數依然保持2.
垃圾回收分代爲何能夠優化性能
如前所述,分代回收能夠提升性能。當堆填滿以後會觸發垃圾回收,垃圾回收器能夠只選擇0代上的對象進行回收,而忽略更高代堆上的對象。然而,因爲越年輕的對象生命週期越短,所以,回收0代堆能夠回收至關多的內存,並且回收所耗的性能也比回收全部代對象要少得多。
這是分代垃圾回收的最簡單優化。分代回收不須要便利整個託管堆,若是一個根對象引用了一個高代對象,那麼垃圾回收器能夠忽略高代對象和其引用對象的遍歷,這會大大減小構建可達對象圖的時間。
若是回收0代對象沒有釋放出足夠的內存,垃圾回收器會嘗試回收1代和0代堆;若是仍然沒有得到足夠的內存,那麼垃圾回收器會嘗試回收2,1,0代堆。具體會回收那一代對象的算法不是肯定的,微軟會持續作算法優化。
多數堆(像c-runtime堆)只要找到足夠的空閒內存就分配給對象。所以,若是我連續分配多個對象時,這些對象的地址空間可能會相差幾M。然而在託管堆上,連續分配的對象的內存地址是連續的。
前面的假設中還提到,新對象之間更可能存在相互引用關係。所以新對象分配到連續的內存上,你能夠得到就近引用的性能優化(you gain performance from locality of reference)。這樣的話極可能你的對象都在CPU的緩存中,這樣CPU的不少操做就不須要去存取內存了。
微軟的性能測試顯示託管堆的分配速度比標準的win32 HeapAlloc方法還要快。這些測試也顯示了200MHz的Pentium的CPU作一次0代回收時間能夠小於1毫秒。微軟的優化目的是讓垃圾回收耗用的時間小於一次普通的頁面錯誤。
使用System.GC類控制垃圾回收
類型System.GC運行開發人員直接控制垃圾回收器。你能夠經過GC.MaxGeneration屬性得到GC的最高代數,目前最高代是定值2.

你能夠調用GC.Collect()方法強制垃圾回收器作垃圾回收,Collect方法有兩個重載:

[csharp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. void GC.Collect(Int32 generation)  
  2.   
  3. void GC.Collect()  

第一個方法容許你指定要回收那一代。你能夠傳0到GC.MaxGeneration的數字作參數,傳0只作0代堆的回收,傳1會回收1代和0代堆,而傳2會回收整個託管堆。而無參數的方法調用GC.Collect(GC.MaxGeneration)至關於整個回收。

在一般狀況下,不該該去調用GC.Collect方法;最好讓垃圾回收器按照本身的算法判斷何時該調用Collect方法。儘管如此,若是你確信比運行時更瞭解何時該作垃圾回收,你就能夠調用Collect方法去作回收。好比說程序能夠在保存數據文件以後作一次垃圾回收。好比你的程序剛剛用完一個長度爲10000的大數組,你再也不須要他了,就能夠把它設置爲null而後執行垃圾回收,緩解內存的壓力。
GC還提供了WaitForPendingFinalizers方法。這個方法簡單的掛起執行線程,知道Freachable隊列中的清空以後,執行完全部隊列中的Finalize方法以後才繼續執行。
GC還提供了兩個方法用來返回某個對象是幾代對象,他們是

 

[csharp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. Int32 GC.GetGeneration(object o);  
  2.   
  3. Int32 GC.GetGeneration(WeakReference wr)  

 

第一個方法返回普通對象是幾代,第二個方法返回弱引用對象的代數。

下面的代碼能夠幫助你理解代的意義:

[csharp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. private static void GenerationDemo() {  
  2. // Let's see how many generations the GCH supports (we know it's 2)  
  3. Display("Maximum GC generations: " + GC.MaxGeneration);  
  4.   
  5. // Create a new BaseObj in the heap  
  6. GenObj obj = new GenObj("Generation");  
  7.   
  8. // Since this object is newly created, it should be in generation 0  
  9. obj.DisplayGeneration(); // Displays 0  
  10.   
  11. // Performing a garbage collection promotes the object's generation  
  12. GC.Collect();  
  13. obj.DisplayGeneration(); // Displays 1  
  14.   
  15. GC.Collect();  
  16. obj.DisplayGeneration(); // Displays 2  
  17.   
  18. GC.Collect();  
  19. obj.DisplayGeneration(); // Displays 2 (max generation)  
  20.   
  21. obj = null; // Destroy the strong reference to this object  
  22.   
  23. GC.Collect(0); // Collect objects in generation 0  
  24. GC.WaitForPendingFinalizers(); // We should see nothing  
  25.   
  26. GC.Collect(1); // Collect objects in generation 1  
  27. GC.WaitForPendingFinalizers(); // We should see nothing  
  28.   
  29. GC.Collect(2); // Same as Collect()  
  30. GC.WaitForPendingFinalizers(); // Now, we should see the Finalize   
  31. // method run  
  32.   
  33. Display(-1, "Demo stop: Understanding Generations.", 0);  
  34. }  
  35. class GenObj{  
  36. public void DisplayGeneration(){  
  37. Console.WriteLine(「my generation is 」 + GC.GetGeneration(this));  
  38. }  
  39.   
  40. ~GenObj(){  
  41. Console.WriteLine(「My Finalize method called」);  
  42. }  
  43. }  

垃圾回收機制的多線程性能優化

在前面的部分,我解釋了GC的算法和優化,而後討論的前提都是在單線程狀況下的。而在真實的程序中,極可能是多個線程一塊兒工做,多個線程一塊兒操縱託管堆上的對象。當一個線程觸發了垃圾回收,其餘全部的線程都應該暫停訪問任何引用對象(包括他們本身棧上引用的對象),由於垃圾回收器有可能要移動對象,修改對象的內存地址。
所以當垃圾回收器開始回收時,全部執行託管代碼的線程必須掛起。運行時有幾種不一樣的機制能夠安全的掛起線程來執行垃圾回收。這一塊的內部機制我不打算詳細說明。可是微軟會持續修改垃圾回收的機制來下降垃圾回收帶來的性能損耗。
下面幾段描述了垃圾回收器在多線程狀況下是如何工做的:
徹底中斷代碼執行 當垃圾回收開始執行時,掛起全部應用程序線程。垃圾回收器隨後將線程掛起的位置記錄到一個just-in-time(JIT)編譯器生成的表中,垃圾回收器負責將線程掛起的位置記錄在表中,記錄當前正在訪問的對象,以及對象存放的位置(變量中,CPU寄存器中,等等)
劫持:垃圾回收器能夠修改線程的棧讓返回地址指向一個特殊的方法,噹噹前執行的方法返回時,這個特殊的方法將會執行,掛起線程,這種改變線程執行路徑的方式稱爲劫持線程。當垃圾回收完成以後,線程會從新返回到以前執行的方法上。
安全點: 當JIT編譯器編譯一個方法時,能夠在某個點插入一段代碼判斷GC是否掛起,若是是,線程就掛起等待垃圾回收完成,而後線程從新開始執行。JIT編譯器插入檢查GC代碼的位置被稱做「安全點」
請注意,線程劫持容許正在執行非託管代碼的線程在垃圾回收過程當中執行。若是非託管代碼不訪問託管堆上的對象時這是沒有問題的。若是這個線程當前執行非託管代碼而後返回執行託管代碼,這個線程將會被劫持,直到垃圾回收完成以後再繼續執行。
除了我剛提到的集中機制以外,垃圾回收器還有其餘改進來加強多線程程序中的對象內存分配和回收。
同步釋放分配(Synchronization-free Allocations):在一個多線程系統中,0代堆被分紅幾個區域,一個線程使用一個區域。這容許多線程同時分配對象,並不須要一個線程獨佔堆。
可伸縮回收(Scalable Collections):在多線程系統中運行執行引擎的服務器版本(MXSorSvr.dll).託管堆會被分紅幾個不一樣的區域,一個CPU一個區域。當回收初始化時,每一個CPU執行一個回收線程,各個線程回收各自的區域。而工做站版本的執行引擎(MXCorWks.dll)不支持這個功能。
大對象回收
這一塊就不翻譯了,有一篇專門的文章談這件事兒
監視垃圾回收
若是你安裝了.Net framework你的性能計數器(開始菜單—管理工具—性能 進入)中就會有.Net CLR Memory一項,你能夠從實例列表中選擇某個程序進行觀察,以下圖所示。


 

這些性能指標的具體含義以下:

性能計數器

說明

# Bytes in all Heaps(全部堆中的字節數)

顯示如下計數器值的總和:「第 0 級堆大小」計數器、「第 1 級堆大小」計數器、「第 2 級堆大小」計數器和「大對象堆大小」計數器。此計數器指示在垃圾回收堆上分配的當前內存(以字節爲單位)。

# GC Handles(GC 處理數目)

顯示正在使用的垃圾回收處理的當前數目。垃圾回收處理是對公共語言運行庫和託管環境外部的資源的處理。

# Gen 0 Collections(第 2 級回收次數)

顯示自應用程序啓動後第 0 級對象(即最年輕、最近分配的對象)被垃圾回收的次數。

當第 0 級中的可用內存不足以知足分配請求時發生第 0 級垃圾回收。此計數器在第 0 級垃圾回收結束時遞增。較高級的垃圾回收包括全部較低級的垃圾回收。當較高級(第 1 級或第 2 級)垃圾回收發生時此計數器被顯式遞增。

此計數器顯示最近的觀察所得值。_Global_ 計數器值不許確,應該忽略。

# Gen 1 Collections(第 2 級回收次數)

顯示自應用程序啓動後對第 1 級對象進行垃圾回收的次數。

此計數器在第 1 級垃圾回收結束時遞增。較高級的垃圾回收包括全部較低級的垃圾回收。當較高級(第 2 級)垃圾回收發生時此計數器被顯式遞增。

此計數器顯示最近的觀察所得值。_Global_ 計數器值不許確,應該忽略。

# Gen 2 Collections(第 2 級回收次數)

顯示自應用程序啓動後對第 2 級對象進行垃圾回收的次數。此計數器在第 2 級垃圾回收(也稱做完整垃圾回收)結束時遞增。

此計數器顯示最近的觀察所得值。_Global_ 計數器值不許確,應該忽略。

# Induced GC(引起的 GC 的數目)

顯示因爲對 GC.Collect 的顯式調用而執行的垃圾回收的峯值次數。讓垃圾回收器對其回收的頻率進行微調是切實可行的。

# of Pinned Objects(釘住的對象的數目)

顯示上次垃圾回收中遇到的釘住的對象的數目。釘住的對象是垃圾回收器不能移入內存的對象。此計數器只跟蹤被進行垃圾回收的堆中的釘住的對象。例如,第 0 級垃圾回收致使僅枚舉第 0 級堆中釘住的對象。

# of Sink Blocks in use(正在使用的接收塊的數目)

顯示正在使用的同步塊的當前數目。同步塊是爲存儲同步信息分配的基於對象的數據結構。同步塊保留對託管對象的弱引用而且必須由垃圾回收器掃描。同步塊不侷限於只存儲同步信息;它們還能夠存儲 COM interop 元數據。該計數器指示與同步基元的過分使用有關的性能問題。

# Total committed Bytes(提交字節的總數)

顯示垃圾回收器當前提交的虛擬內存量(以字節爲單位)。提交的內存是在磁盤頁面文件中保留的空間的物理內存。

# Total reserved Bytes(保留字節的總數)

顯示垃圾回收器當前保留的虛擬內存量(以字節爲單位)。保留內存是爲應用程序保留(但還沒有使用任何磁盤或主內存頁)的虛擬內存空間。

% Time in GC(GC 中時間的百分比)

顯示自上次垃圾回收週期後執行垃圾回收所用運行時間的百分比。此計數器一般指示垃圾回收器表明該應用程序爲收集和壓縮內存而執行的工做。只在每次垃圾回收結束時更新此計數器。此計數器不是一個平均值;它的值反映了最近觀察所得值。

Allocated Bytes/second(每秒分配的字節數)

顯示每秒在垃圾回收堆上分配的字節數。此計數器在每次垃圾回收結束時(而不是在每次分配時)進行更新。此計數器不是一段時間內的平均值;它顯示最近兩個樣本中觀測的值的差除以取樣間隔時間所得的結果。

Finalization Survivors(完成時存留對象數目)

顯示因正等待完成而從回收後保留下來的進行垃圾回收的對象的數目。若是這些對象保留對其餘對象的引用,則那些對象也保留下來,但此計數器不對它們計數。「從第 0 級提高的完成內存」和「從第 1 級提高的完成內存」計數器表示因完成而保留下來的全部內存。

此計數器不是累積計數器;它在每次垃圾回收結束時由僅在該特定回收期間存留對象的計數更新。此計數器指示因爲完成應用程序可能致使系統開銷太高。

Gen 0 heap size(第 2 級堆大小)

顯示在第 0 級中能夠分配的最大字節數;它不指示在第 0 級中當前分配的字節數。

當自最近回收後的分配超出此大小時發生第 0 級垃圾回收。第 0 級大小由垃圾回收器進行微調而且可在應用程序執行期間更改。在第 0 級回收結束時,第 0 級堆的大小是 0 字節。此計數器顯示調用下一個第 0 級垃圾回收的分配的大小(以字節爲單位)。

此計數器在垃圾回收結束時(而不是在每次分配時)進行更新。

Gen 0 Promoted Bytes/Sec(從第 1 級提高的字節數/秒)

顯示每秒從第 0 級提高到第 1 級的字節數。內存在從垃圾回收保留下來後被提高。此計數器是每秒建立的在至關長時間保留下來的對象的指示符。

此計數器顯示在最後兩個樣本(以取樣間隔持續時間來劃分)中觀察到的值之間的差別。

Gen 1 heap size(第 2 級堆大小)

顯示第 1 級中的當前字節數;此計數器不顯示第 1 級的最大大小。不直接在此代中分配對象;這些對象是從前面的第 0 級垃圾回收提高的。此計數器在垃圾回收結束時(而不是在每次分配時)進行更新。

Gen 1 Promoted Bytes/Sec(從第 1 級提高的字節數/秒)

顯示每秒從第 1 級提高到第 2 級的字節數。在此計數器中不包括只因正等待完成而被提高的對象。

內存在從垃圾回收保留下來後被提高。不會從第 2 級進行任何提高,由於它是最舊的一級。此計數器是每秒建立的很是長時間保留下來的對象的指示符。

此計數器顯示在最後兩個樣本(以取樣間隔持續時間來劃分)中觀察到的值之間的差別。

Gen 2 heap size(第 2 級堆大小)

顯示第 2 級中當前字節數。不直接在此代中分配對象;這些對象是在之前的第 1 級垃圾回收期間從第 1 級提高的。此計數器在垃圾回收結束時(而不是在每次分配時)進行更新。

Large Object Heap size(大對象堆大小)

顯示大對象堆的當前大小(以字節爲單位)。垃圾回收器將大於 20 KB 的對象視做大對象而且直接在特殊堆中分配大對象;它們不是經過這些級別提高的。此計數器在垃圾回收結束時(而不是在每次分配時)進行更新。

Promoted Finalization-Memory from Gen 0(從第 1 級提高的完成內存)

顯示只因等待完成而從第 0 級提高到第 1 級的內存的字節數。此計數器不是累積計數器;它顯示在最後一次垃圾回收結束時觀察到的值。

Promoted Finalization-Memory from Gen 1(從第 1 級提高的完成內存)

顯示只因等待完成而從第 1 級提高到第 2 級的內存的字節數。此計數器不是累積計數器;它顯示在最後一次垃圾回收結束時觀察到的值。若是最後一次垃圾回收就是第 0 級回收,此計數器則重置爲 0。

Promoted Memory from Gen 0(從第 1 級提高的內存)

顯示在垃圾回收後保留下來而且從第 0 級提高到第 1 級的內存的字節數。此計數器中不包括那些只因等待完成而提高的對象。此計數器不是累積計數器;它顯示在最後一次垃圾回收結束時觀察到的值。

Promoted Memory from Gen 1(從第 1 級提高的內存)

顯示在垃圾回收後保留下來而且從第 1 級提高到第 2 級的內存的字節數。此計數器中不包括那些只因等待完成而提高的對象。此計數器不是累積計數器;它顯示在最後一次垃圾回收結束時觀察到的值。若是最後一次垃圾回收就是第 0 級回收,此計數器則重置爲 0。

這個表來自MSDN