基礎知識:CLR垃圾回收器採用代(generation)機制,目前支持0、1、2三代。
1、新構造添加到堆的對象稱爲第0代。
2、經過對第0代的垃圾回收之後,第0代的倖存者被提升至第1代。
3、經過對第1代的垃圾回收之後,第一代的倖存者被提升至第2代。
CLR初始化時,會爲每一代選擇預算。第0代的預算約爲256K,第1代預算約2M,第2代預算約10M。在實際使用過程中,垃圾回收器會用類似啓發式算法調整各代的預算。
實例:該實例運行在.NET4.0環境
internal class Program { private static void Main(string[] args) { StringBuilder sb = new StringBuilder(); Console.WriteLine("創建Datatable前:" + GC.GetTotalMemory(true)/(1024) + "K"); DataTable table = new DataTable("ParentTable"); Console.WriteLine("創建DataTable後對象代數:" + GC.GetGeneration(table) + "代"); DataColumn column; DataRow row; column = new DataColumn(); column.DataType = System.Type.GetType("System.Int32"); column.ColumnName = "id"; column.Unique = true; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "ParentItem"; column.AutoIncrement = false; column.Caption = "ParentItem"; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "ChildrenItem"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item1"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item2"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item3"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item4"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item5"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item6"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item7"; column.AutoIncrement = false; table.Columns.Add(column); column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "Item8"; column.AutoIncrement = false; table.Columns.Add(column); for (int i = 0; i < 2000; i++) { row = table.NewRow(); row["id"] = i; row["ParentItem"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["ChildrenItem"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item1"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item2"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item3"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item4"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item5"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item6"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item7"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; row["Item8"] = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; table.Rows.Add(row); } Console.WriteLine("爲Datatable添加數據後佔用內存:" + GC.GetTotalMemory(true)/(1024) + "K"); List<Nodes> list = new List<Nodes>(); Nodes nodes; for (int i = 0; i < 2000; i++) { nodes = new Nodes(); nodes.Id = i; nodes.Name1 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name2 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name3 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name4 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name5 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name6 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name7 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name8 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name9 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; nodes.Name10 = "垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試垃圾回收測試 " + i; list.Add(nodes); } Console.WriteLine("創建List後佔用內存:" + GC.GetTotalMemory(true)/(1024) + "K"); Console.WriteLine("DataTable代數:" + GC.GetGeneration(table)); Console.WriteLine("List代數:" + GC.GetGeneration(list)); Console.WriteLine("SB代數:" + GC.GetGeneration(sb)); Console.ReadLine(); } } public class Nodes { public int Id { get; set; } public string Name1 { get; set; } public string Name2 { get; set; } public string Name3 { get; set; } public string Name4 { get; set; } public string Name5 { get; set; } public string Name6 { get; set; } public string Name7 { get; set; } public string Name8 { get; set; } public string Name9 { get; set; } public string Name10 { get; set; } }
運行結果:
問題提出:
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)。
參考CLR via C#(第三版)第21章 自動內存管理