基礎知識:CLR垃圾回收器采用代(generation)機制,目前支持0、1、2三代。
1、新構造添加到堆的對象稱為第0代。
2、經過對第0代的垃圾回收之后,第0代的幸存者被提升至第1代。
3、經過對第1代的垃圾回收之后,第一代的幸存者被提升至第2代。
CLR初始化時,會為每一代選擇預算。第0代的預算約為256K,第1代預算約2M,第2代預算約10M。在實際使用過程中,垃圾回收器會用類似啟發式算法調整各代的預算。
實例:該實例運行在.NET4.0環境
View Code
運行結果:
問題提出:
1、為什么table、list、sb都為2代對象?
程序的開始創建了StringBuilder的實例sb和DataTable的實例table,由GC.GetGeneration(table)方法可知table和sb這兩個對象在剛創建之后屬于第0代。
當通過for循環為table添加數據時很快超出了第0代256K內存的預算,這個時候CLR會啟動一次垃圾回收,垃圾回收器檢測內存中的sb對象,發現sb對象被后面的Console.WriteLine("SB代數:" + GC.GetGeneration(sb));引用,所以沒被回收。由于table占有的內存迅速增加,并且在經過一次垃圾回收后sb幸存下來,所以此時sb和table都被提升到1代。0代內存空出來。
1代的內存預算是2M,從運行結果可以看出終table占有的內存為3.5M,也就是說table的數據增加也會超出1代內存預算。在1代內存預算快被超出的時候,CLR啟動垃圾回收器,檢查第1代和第0代中的所有對象。但發現sb對象仍然被引用,沒被回收,所以sb對象在回收第1代和第0代的回收之后幸存下來,所以sb對象被提升到第2代。Table對象由于超過1代的內存預算,也被提升到第2代。1代內存空出來。
同理也可以得出list對象由于超出1代的內存預算被提升到2代。在這個實例中如果把list的for循環調整到2000000時,會導致OutOfMemoryException異常。因為list占用的內存迅增加,垃圾回收器執行一次完整的回收之后還不能滿足list的需要,所以拋出OutOfMemoryException異常。
對象被提升到2代這個過程中,會多次啟動垃圾回收器,對性能有一定的影響,并且由于table和list的數據量比較大,同時也成為大對象。回收大對象損失的性能更多。在這個實例中,從運行結果可以看出2000條的數據量table占用的內存比list占用的內存多340K,這個數量比0代的內存預算還要大。
建議:
1、在項目開發中,如果底層不需要用到DataTable自帶的一些功能(如select(),compute()等方法),而只是用來數據傳輸,個人建議采用List<T>的方式。因為它占用的內存比DataTable小,同時在list被回收時性能損失更小。
2、如果對象有可能為大對象,可以使用GC.GetTotalMemory(true)方法來測定。評估之后可能會成為大對象則建議分割該對象或者采用非托管方式(可以啟用unsafe)。
1、新構造添加到堆的對象稱為第0代。
2、經過對第0代的垃圾回收之后,第0代的幸存者被提升至第1代。
3、經過對第1代的垃圾回收之后,第一代的幸存者被提升至第2代。
CLR初始化時,會為每一代選擇預算。第0代的預算約為256K,第1代預算約2M,第2代預算約10M。在實際使用過程中,垃圾回收器會用類似啟發式算法調整各代的預算。
實例:該實例運行在.NET4.0環境
View Code
運行結果:
問題提出:
1、為什么table、list、sb都為2代對象?
程序的開始創建了StringBuilder的實例sb和DataTable的實例table,由GC.GetGeneration(table)方法可知table和sb這兩個對象在剛創建之后屬于第0代。
當通過for循環為table添加數據時很快超出了第0代256K內存的預算,這個時候CLR會啟動一次垃圾回收,垃圾回收器檢測內存中的sb對象,發現sb對象被后面的Console.WriteLine("SB代數:" + GC.GetGeneration(sb));引用,所以沒被回收。由于table占有的內存迅速增加,并且在經過一次垃圾回收后sb幸存下來,所以此時sb和table都被提升到1代。0代內存空出來。
1代的內存預算是2M,從運行結果可以看出終table占有的內存為3.5M,也就是說table的數據增加也會超出1代內存預算。在1代內存預算快被超出的時候,CLR啟動垃圾回收器,檢查第1代和第0代中的所有對象。但發現sb對象仍然被引用,沒被回收,所以sb對象在回收第1代和第0代的回收之后幸存下來,所以sb對象被提升到第2代。Table對象由于超過1代的內存預算,也被提升到第2代。1代內存空出來。
同理也可以得出list對象由于超出1代的內存預算被提升到2代。在這個實例中如果把list的for循環調整到2000000時,會導致OutOfMemoryException異常。因為list占用的內存迅增加,垃圾回收器執行一次完整的回收之后還不能滿足list的需要,所以拋出OutOfMemoryException異常。
對象被提升到2代這個過程中,會多次啟動垃圾回收器,對性能有一定的影響,并且由于table和list的數據量比較大,同時也成為大對象。回收大對象損失的性能更多。在這個實例中,從運行結果可以看出2000條的數據量table占用的內存比list占用的內存多340K,這個數量比0代的內存預算還要大。
建議:
1、在項目開發中,如果底層不需要用到DataTable自帶的一些功能(如select(),compute()等方法),而只是用來數據傳輸,個人建議采用List<T>的方式。因為它占用的內存比DataTable小,同時在list被回收時性能損失更小。
2、如果對象有可能為大對象,可以使用GC.GetTotalMemory(true)方法來測定。評估之后可能會成為大對象則建議分割該對象或者采用非托管方式(可以啟用unsafe)。