C#内存管理与垃圾回收
垃圾回收分两步:1:标记;2:压缩
1:标记。在垃圾回收开始的时候,垃圾回收器视托管堆中的所有对象都为垃圾,即线程栈上没有指针指向托管堆。这样的估计是因为一个对象被视为垃圾就是它没有被引用,当垃圾回收开始的时候,垃圾回收器会沿着线程栈线性扫描,当线程栈上的一个变量引用了托管堆中的对象时,垃圾回收器就会将这个对象标记,即修改该对象同步块索引中的一个特定的位,同步块索引就是一个bit数组,每一个元素都有它特定的作用,上面就列出了我所知道的三个功能。被标记的对象也可能引用其他的对象,而被引用的对象同样会被标记,垃圾回收器是用递归的方式将这些对象一一标记的,一个对象可能会被多个对象引用,当垃圾回收器发现某个对象被标记时就会退出递归,因为再往下递归完全是多余,而且还可能出现死循环。
垃圾回收器就这样线性的扫描线程栈,递归的扫描托管堆,最后将托管堆中所有被引用的对象标记,而没有被标记的对象就是垃圾,等着被回收。
2:压缩。当垃圾被回收之后,就会出现磁盘碎片,那么就要对托管堆进行整理,即压缩。将没有被回收的对象放在一起,靠近托管堆开始的位置,将剩余的内存腾出空间来以便存放新的对象。由于压缩很多对象就会移动位置,而引用他们的指针都会变得无效,所以托管堆要修改所有指针的指向,以保证不会因为垃圾回收而让对象变得不可到达,指针变得无效。
压缩完了之后,又腾出了空间,又可以分配新的对象,当第0代满了之后又进行垃圾回收,垃圾回收就这样一直进行着,直到回收了3代还是没有内存可以分配,那就是弹尽粮绝的时候了,CLR会告诉你OutOfMemoryException。CLR的内存被的程序吃光了。更多关于代的信息,可以看我的这篇博客。在第0代满的时候就会进行垃圾回收,第0代回收完之后还是没有足够的内存存放当前对象就回收第1代,如果还是不够就回收第2代,够就不回收下一代,垃圾回收还可以用代码控制GC.Collect()。
垃圾回收--代 代是CLR垃圾回收器采用的一种机制,他唯一的目的就是提升应用程序的性能,采用代的垃圾回收器做到了一下几点:
1:对象越新,生存周期越短,跟栈的原理很像,先进后出,先定义的局部变量,在栈中停留的时间相对长一点。
2:对象越老,生存周期越长,后面解释。
3:回收堆的一部分,速度快于回收整个堆,那是肯定的,就是为了实现只回收一部分内存中的数据,才产生了代的概念,大多数时间只回收第0代。
第0代:托管堆在初始化的时候不包含任何对象,新分配在堆上的对象被称为第0代,垃圾回收器从没有检查过他,CLR在初始化的
时候,会为第0代预算了一个容量,假设为256KB,容量一般为128KB的整数倍,这跟CPU的L2缓存容量有关,CPU的L2缓存容量经
历了128KB,256KB,512KB,1M......,就是为了让第0代中的数据能全部装入CPU的L2缓存中,这样处理数据会更快。
第1代:如果在分配一个新对象时,使第0代中的数据超过其预算容量,就会发生垃圾回收,不能到达的数据就会被回收,不能到
达就是没有被任何对象引用,有被引用当然就不会被回收,没有被回收的数据,被称为第1代,第一代的容量比第0代的大,假设
为2M,经过一次垃圾回收后第0代不包含任何数据。只要第1代没有就不会回收。
新产生的对象永远被分配在第0代中,第0代中的数据满了,就发生垃圾回收,将没有被回收的对象压缩至第1代,数据压缩相当耗
性能,就是把数据从内存在的A出复制到内存的B处,只要第1代中的数据没有满,垃圾回收器就只会回收第0代中的数据,这就是
部分回收,当第1代中的数据满了,垃圾回收器就会同时回收第0代和第1代,第0代没有被回收的数据就会被压缩到第1代中,直至
第1代也满。
第2代:当第1代中的数据满了,就会回收第1代,没有被回收的数据就被称为第2代,第2代的容量大于第1代,托管堆只支持3代
,第0代,第1代,第2代。
CLR假设新生成的对象生存周期较短,所以每次回收第0代都能回收大量内存,所以CLR总是针对第0代疯狂的回收,这样效率高吗
?哈哈哈!这样可能导致第0代被回收了很多次,第1代一次也没有被回收,当然第1代中大垃圾也就没有被回收,CLR假设活得比
较久的对象能继续活下去,这就是对象越老,生存周期越长。
托管堆只实现部分回收,即回收第0代,由于第0代的容量较小,所以每次垃圾回收的速度较快,当然也会因为第0代容量较小,发
生垃圾回收的频率会高一些,为了得到更好的垃圾回收性能,所以第0代的容量是动态的,如果每次执行垃圾回收,所有的垃圾都
被回收了,那么托管预算第0代的容量就会减少,这样可以加快垃圾回收的速度,如果每次执行垃圾回收,都有大量的垃圾没有被
回收,从第0代变成第1代,托管堆就会将第0代的预算容量变大,这样可以减少数据的转移。数据的转移就相当于整理内存碎片,
对程序的性能,影响还是蛮大的。
只回收第0代的还有一个好处就是,如果第0代中的数据引用了第1代中的数据,第1代中的数据不用被检查,第1代中被引用的对象
内部结构,垃圾回收器也不会管,回收更快啊,当然第1代中的数据也可能引用第0代中的数据,若第1代中的对象引用了第0代中
的对象,第1代中的对象就会被标记,只有重上一次垃圾回收到下一次垃圾回收被标记的对象才会被检查,当然要检查第1代中的
数据啊,你不检查第1代中的数据,你怎么知道第0代中的数据不可到达,没有被引用呢,第1代中,只有被标记的对象才会被检查
,这样同样能提高垃圾回收的性能。总之,微软设计的这个垃圾回收器处处在提高你的程序性能,他是这么说的,你感觉到了吗?
如果回收3代还是没有内存可分配,就抛出一个OutOfMemoryException异常,告诉你没有内存可分配了,以前在开发一个项目中
就偶尔抛出这个异常,当时我们都不知道怎么回事,原因就是我们构造一个大的Json对象,用的是string+string,导致内存被
吃光了,要是当时知道用StringBuilder,性能将会大大的提高,也不至于OutOfMemoryException异常啊