.NET 内存管理和垃圾回收
C/C++ 程序需要开发者手动分配和释放内存,.Net程序则使用垃圾回收技术自动收集不再使用的内存。垃圾回收器(GC)使用引用
跟踪占用内存的对象,如果对象被设置为null或已不在使用范围,GC就会标志该对象为可回收,这样GC就可以回收被这些对象占用的内存。
垃圾回收器(GC)使用Win32? VirtualAlloc() 接口为自己的堆分配内存,.Net托管堆是一个巨大连续的虚拟内存。GC先预留虚拟内存,当托管堆增长时则提交内存。GC跟踪托管堆末尾可用的地址并把下一个分配请求放置在这个地址。这样.Net 托管内存分配的托管堆是连续相邻的。这极大提高分配时间,因为无需从内存块链接表去搜索。当程序运行一段时间后,由于对象的删除会在托管堆产生碎片,当垃圾回收发生时,GC通过直接内存拷贝来压缩堆并填充这些碎片。
代(generations)
垃圾回收器(GC)通过把对象生命周期划分为代的方式提高内存管理效率,当回收开始时,年轻一代的对象先被回收,如果这样内存还不够,接着老一代的会被回收。代的使用意味着GC每次只和已分配对象集合的一个子集打交道。GC现在使用三个代,分别为gen0,gen1,gen2。分配对象开始属于gen0,所有在gen0的对象在经历一次回收后仍然存在的则提升到gen1, 在gen1的对象经历gen0,gen1回收后还存在的则提升到gen2
逐步地,最老一代的会填充最老的对象。这些老时代的对象会变得更稳定、所需更少的回收、更少的内存拷贝。
某个代的回收是当这个代的内存阈值被突破后发生,在.Net 1.0, 最初的gen0,gen1,gen2的阈值为256KB,2MB,10MB. 注意,GC会动态根据程序的内存分配模式来调整代的阈值。超过85KB的对象会自动放置到大对象堆(LOH),LOH的处理下面单独讨论
树根(root)
GC使用对象引用判断托管堆的某个内存块是否可以被回收。与其他垃圾回收技术不同,.Net垃圾回收没有为内存块设置堆标志(heap flag)来指示该内存块是否可以被回收。 对每个程序,GC维护一个引用树来跟踪程序使用的对象。
GC认为一个对象可以被root如果这个对象至少有一个父亲对象拥有一个引用指向它。每个.Net程序都有一套root的集合包括全局和静态对象,以及关联的线程堆栈和动态生成的对象。在开始垃圾回收前,GC从root开始往下建立所有变量引用的树。GC同时建立一个所有活动对象的主列表,然后遍历托管堆查找那些不在这个活动对象列表里的对象。
大对象堆(LOH)
.Net内存管理器把超过85K的分配都放到一个单独的堆,称为大对象堆。这个堆包含一系列虚拟内存块独立于主托管堆。为大对象使用单独的堆使得主托管堆的垃圾回收更有效率,因为回收需要移动内存,而移动大块内存是很耗时的,而且大对象堆是从来不压缩的。
例如,如果你为一个单独的块分配1MB内存,LOH则扩展到1MB,当你释放这个对象,LOH不会释放该内存块,所以LOH还是1MB,如果你又分配500KB对象,新的内存块就分配在那1MB内。在进程生命周期内,LOH会一直增长来容纳所有大的内存块分配,但从不会缩小即使对象已经释放,即使发生垃圾回收。
以上翻译节选自MSDN