我眼中的.NET平台下垃圾收集

  对于C、C++程序员来说,是没有垃圾收集器的,这就意味着程序员必须抽出时间来关心内存的问题,自己分配的内存空间需要自己手动的释放掉。这样往往会引发二个问题:

  1.内存泄露(没有释放掉已分配的内存空间)

  2.访问了已经释放的内存空间。

  上述2个问题,往往会让程序变得很不稳定,变得不可预测。为了避免这些问题,同时把程序员解放出来,Java、.Net平台出现了垃圾回收器这个概念。记得,曾经听到过这就得比喻,在国外的麦当劳,顾客是需要自己收拾吃剩的食物(明显是C、C++程序员)。而在中国,顾客是上帝,你吃完东西可以直接转身离开。(服务员帮你收集,这里可以垃圾为垃圾收集器!)

  想必大家已经了解为什么需要垃圾收集器?那接着我们肯定比较关系How(垃圾收集器是怎么样工作?)

  首先,我们需要了解.NET平台下,CLR在托管堆上分配内存空间,而且这些空间是连续的。比如有A、B、C三个对象在托管堆上,有个NextObjPtr指针作为每次分配内存空间的起始位置。

A

B

C

 

                                                                                  |NextObjPtr指向C的末端 图1

  可想而知,这些内存空间并不是无限的,下次分配空间时发现空间不足就要进行垃圾收集,那怎么样判断托管堆上的对象是垃圾呢?这里我们将引入一个根的概念,根包括CPU寄存器、静态变量、局部引用类型变量、方法参数。在垃圾收集时,会检查是否有变量持有这四种根的引用,如果有,认为这个对象是可达到,否则,这个对象是不可达到的。不可达到的对象就被认为是垃圾会被回收掉。比如垃圾收集器检查发现A、C、F、G不可达:

A

B

C

D

E

F

G

H

 

                                      |NextObjPtr 图2

  垃圾回收器执行后:

B

D

E

H

 

                                |NextObjPtr 图3

  我们可以发现回收内存时进行了内存压缩,保证托管堆上的内存分配仍然是连续的。你可能会担心性能的问题,如果每次都对整个托管推进行扫描性能一定很低,我们能不能每次对托管堆进行局部扫描呢?为了提高性能,.NET引入了代的概念!

  .NET将托管推分为第0代,第1代,第2代。当第0代充满时(大小256KB),进行检查,释放不可达到的对象,可达到的对象成为第一代,这时第0代就被清空了,这样下次进行垃圾检查的时候,就这只对第0代进行检查。除非第1代充满时(大小2M)才进行垃圾检查,这时第1代中不可达的对象被回收掉,可达的对象转为第2代。第2代同样是在充满时(大小10M)才进行垃圾收集。引入代的机制,就很好地避免了全盘扫描托管堆,很好地提升了性能!

  说到这里,你可能觉得垃圾收集器这个服务器是万能的?No!注意,垃圾收集器只能操作托管资源,即CLR世界的资源,托管堆上的资源,它对非托管资源,如数据库连接,文件资源,操作系统的内核对象等这些资源是无能为力的。所以使用这些资源的时候往往需要我们显示的释放掉它。比如我们在使用数据库连接的时候,会使用Close或者是Dispose方法去释放掉它,在使用FileStream,StreamReader的时候都要关闭这些对象。

posted @ 2012-06-21 21:52  _小阳  阅读(320)  评论(0编辑  收藏  举报