.Net的垃圾回收机制(GC)之拙见——托管类型的垃圾回收

各种语言的垃圾回收在IT界噪的沸沸扬扬,有极大的优化同时也有瓶颈。

而在.Net中的垃圾回收机制又是怎样的呢?

众所知周,.Net中的垃圾回收机制是由.Net Framework托管的,带给开发者最大的好处就是省去了手动进行垃圾回收的麻烦。

在虚拟内存当中,有个区域称为“栈”,这个数据结构完全符合“先进后出(FILO)”的特性。

在C#中,有个名词叫作用域,作用域表示了一个变量的作用范围,比如

{
int a;
//do something
{
int b;
//do something
}
}

声明变量a之后,在内部代码块中声明变量b,然后内部代码块终止,b超出了作用域,然后变量a所在的代码块终止,a超出了作用域,所以b的作用域始终包含在a的作用域当中。

在栈当中,当声明一个变量时,在栈开辟一个空间存放,当这个变量超过变量域,栈把用于这个变量的内存释放,这就是栈的工作方式。

.Net的垃圾回收机制(GC)之拙见——托管类型的垃圾回收

当代码执行到变量b所在的内部代码块的时候,栈指针如图所示,但该内部代码块执行结束之后,b超出了作用域,栈指针会移动到变量a所在的内存区域,也就是799997所在的字节内存处。此时如果声明新的变量c,c会存放在从799996开始的一段内存空间中,这些空间以前是存储变量b的。

.Net垃圾回收机制会根据这个规律按部就班执行下去,保证栈内存的操作不会出现差错,当编译器遇到int i,j这样的代码行,同时声明两个变量时,这两个变量进入作用域的顺序是不确定的,但这两个变量是同时声明的,也必须是同超出作用域的,所以编译器会随机把这两个变量放到栈内存当中,超出作用域时会确保把先进入栈的那个变量后删除,这样就能保证该规则不会与变量的生存期冲突。

C#的另外一个名词——“堆”,堆的出现是为了降低编译器对代码声明的所有类型处理的复杂性,和栈配合可以更加有效清楚地管理内存。通常我们希望使用一个方法分配内存来存储一些数据,并在方法退出后的一段时间内数据仍是可用的。只要是用new运算符来请求分配存储空间的,就存在这种可能性——例如对于所有的引用类型。此时就要使用托管堆。

 void func(){
Student stu1;
stu1 = new Student();
Student stu2 = new Student();
stu3 = stu1;
}

堆的工作原理跟栈相似,只是存储了不同的对象。当以上代码执行时,在堆栈当中的内存情况如下所示:

.Net的垃圾回收机制(GC)之拙见——托管类型的垃圾回收

不同的是堆内存是向上分配的(冒号:后面的值表示存储的内容,此处不再标出栈内存的地址)。对于变量stu1,stu2,stu3的声明和在栈中的内存分配情况在前面已经交代清楚,声明 Student stu1; 时,栈为变量stu1分配一段空间,此时stu1存储的值并没有初始化,当代码运行到 stu1 = Student(); 时,在堆内存中分配了一个Student实例,即200000 ~ 2000031段(假设分配给Student对象的内存大小为32),并把这段内存的首地址复制给栈中的stu1。执行 Student stu2 = new Student() 时,把栈上的4个字节分配给stu2,然后stu2的对象在堆上从200032开始向上分配空间,并把200032地址赋值给stu2。然后声明stu3,存储内容为stu1的实例,因此最终栈中存储了3个变量,堆中存储了2个对象,有两个变量(stu1,stu3)指向了同一个对象。当func()函数运行结束后,所有变量均超出了作用域,因此会以stu3,stu2,stu1的顺序依次释放,栈指针移向原先stu1变量所在内存的上一个字节内存处。对于堆中的对象,当一个对象检测到没有变量指向它时,即把该对象所占用的内存自动释放出来。先释放stu2实例,再释放stu1实例。

.Net的垃圾回收机制(GC)之拙见——托管类型的垃圾回收

这样一来,因为对象的存在周期不同,会导致堆内存中已用堆块和空闲堆块随机存在。

按照以往类似C++编译器的特性,当此时需要new一个新的对象时,会在堆中找一块大小适合的内存用于存放新的对象,在这个过程中会稍微降低new一个新对象的效率,在C#编译器中,但执行垃圾回收时,编译器会把所有的已使用的空间按照原先的顺序拼凑在一起,形成一段连续使用的空间。.Net的垃圾回收机制(GC)之拙见——托管类型的垃圾回收

然后把堆指针指向最上已使用的空间(已使用3),这样C#在new新对象的时候,就直接在堆指针的地方直接分配,这样一来直接导致了C#在new新对象的时候具有极高的效率(和其他语言相比)。但编译器在整理堆内存的时候也会造成额外的开销,则在整理时候应用程序必须是停止运行状态,所以最好让编译器自己决定进行垃圾回收的时间,而不是让应用程序自己决定(GC.Collect()),造成额外开销和提高new效率,权衡下来还是会提高总体的运行效率。这也是.Net垃圾回收机制和其他垃圾回收机制比较大的不同点。

以上为个人对.Net垃圾回收机制的一些见解,内容拙劣,欢迎斧正。

尊重知识产权,转载引用请通知作者并注明出处!

上一篇:分布式系统状态下redis存储asp.net session使用第三方Providers驱动


下一篇:Linux防火墙iptables详解