.NET垃圾回收 问题、建议
基础知识:CLR垃圾回收器采用代(generation)机制,目前支持0、1、2三代。
1、新构造添加到堆的对象称为第0代。
2、经过对第0代的垃圾回收之后,第0代的幸存者被提升至第1代。
3、经过对第1代的垃圾回收之后,第一代的幸存者被提升至第2代。
CLR初始化时,会为每一代选择预算。第0代的预算约为256K,第1代预算约2M,第2代预算约10M。在实际使用过程中,垃圾回收器会用类似启发式算法调整各代的预算。
实例:该实例运行在.NET4.0环境
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
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章 自动内存管理
http://www.cnblogs.com/jintianzhang/archive/2013/04/29/3050459.html