基础知识: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)。