今天来谈谈C# 的GC ,也就是垃圾回收机制,非常的受教,总结如下
首先:谈谈托管,什么叫托管,我的理解就是托付C# 运行环境帮我们去管理,在这个运行环境中可以帮助我们开辟内存和释放内存,开辟内存一般用new ,内存是随机分配的,释放主要靠的是GC 也就是垃圾回收机制。哪么有两个大问题 1.GC 可以回收任何对象吗?2.GC 什么时候来回收对象?回收那些对象?
对于第一个问题,GC 可以回收任何对象吗?我是这样理解的,首先要明白一点,C# 在强大也管不到非托管代码?哪么什么是非托管代码呢?比如stream (文件),connection (数据库连接),COM (组件)等等。。哪么这些对象是需要进行连接的,比如说我们写这样一句话FileStream fs = new FileStream(“d://a.txt”,FileMode.Open); 实际上已经创建了和d://a.txt 的连接,如果重复两次就会报错。哪么fs 这个对象叫做非托管对象,也就是说C#
不能自动去释放和d://a.txt 的连接。哪么对于非托管的代码怎么办,一会我来说。
对于第二个问题,GC 什么时候来回收,回收什么对象?我想后面的就不用我说了,当然是回收托管对象了。但是GC 什么时候回收?是这样的:GC 是随机的,没有人知道他什么时候来,哪么我写了一个例子,证明这一点
private void button1_Click(object sender, EventArgs e)
{
AA a = new AA ();
AA b = new AA ();
AA c = new AA ();
AA d = new AA ();
}
public class AA{}
在讲这个例子之前,要明白什么被称之为垃圾,垃圾就是一个内存区域,没有被任何引用指向,或者不再会被用到。 哪么在第一次点击按钮的时候会生成4 个对象,第二次点击按钮的时候也会生成4 个对象,但是第一次生成的4 个对象就已经是垃圾了,因为,第一次生成的4 个对象随着 button1_Click 函数的结束而不会再被调用(或者说不能再被调用 ),哪么这个时候GC 就会来回收吗?不是的!我说了GC 是随机的,哪么你只管点你的,不一会GC 就会来回收的(这里我们可以认为,内存中存在一定数量的垃圾之后,GC 会来 ),要证明GC 来过我们把AA 类改成
public class AA
{
~AA()
{
MessageBox .Show(" 析构函数被执行了" );
}
}
要明白,GC 清理垃圾,实际上是调用析构函数,但是这些代码是托管代码(因为里面没有涉及到Steam,Connection 等。。)所以在析构函数中,我们可以只写一个MsgBox 来证明刚的想法;这个时候,运行你的程序,一直点击按钮,不一会就会出现一大堆的“ 析构函数被执行了”…
好了,然后让我们看看能不能改变GC 这种为所欲为的天性,答案是可以的,我们可以通过调用GC.Collect(); 来强制GC 进行垃圾回收,哪么button1_Click 修改如下
private void button1_Click(object sender, EventArgs e)
{
AA a = new AA ();
AA b = new AA ();
AA c = new AA ();
AA d = new AA ();
GC .Collect();
}
哪么在点击第一次按钮的时候,生成四个对象,然后强制垃圾回收,这个时候,会回收吗?当然不会,因为,这四个对象还在执行中(方法还没结束),当点第二次按钮的时候,会出现四次" 析构函数被执行了" , 这是在释放第一次点击按钮的四个对象,然后以后每次点击都会出现四次" 析构函数被执行了" ,哪么最后一次的对象什么时候释放的,在关闭程序的时候释放(因为关闭程序要释放所有的内存)。
好了,现在来谈谈非托管代码,刚才说过,非托管代码不能由垃圾回收释放,我们把AA 类改成如下
public class AA
{
FileStream fs = new FileStream ("D://a.txt" ,FileMode .Open);
~AA()
{
MessageBox .Show(" 析构函数被执行了" );
}
}
private void button1_Click(object sender, EventArgs e)
{
AA a = new AA ();
}
如果是这样一种情况,哪么第二次点击的时候就会报错,原因是一个文件只能创建一个连接。哪么一定要释放掉第一个资源,才可以进行第二次的连接。哪么首先我们想到用 GC .Collect() ,来强制释放闲置的资源,修改代码如下:
private void button1_Click(object sender, EventArgs e)
{
GC .Collect();
AA a = new AA ();
}
哪么可以看到,第二次点按钮的时候,确实出现了“析构函数被执行了“, 但是程序仍然错了,原因前面我说过,因为Stream 不是托管代码,所以C# 不能帮我们回收,哪怎么办?
自己写一个Dispose 方法;去释放我们的内存。代码如下:
public class AA :IDisposable
{
FileStream fs = new FileStream ("D://a.txt" ,FileMode .Open);
~AA()
{
MessageBox .Show(" 析构函数被执行了" );
}
#region IDisposable 成员
public void Dispose()
{
fs.Dispose();
MessageBox .Show("dispose 执行了" );
}
#endregion
}
好了,我们看到了,继承 IDisposable 接口以后会有一个Dispose 方法(当然了,你不想继承也可以,但是接口给我们提供一种规则,你不愿意遵守这个规则,就永远无法融入整个团队,你的代码只有你一个人能看懂),好了闲话不说,这样一来我们的 button1_Click 改为private void button1_Click(object sender, EventArgs e)
{
AA a = new AA ();
a.Dispose();
}
我们每次点击之后,都会发现执行了“ dispose 执行了”,在关闭程序的时候仍然执行了“析构函数被执行了”这意味了,GC 还是工作了,哪么如果程序改为:
private void button1_Click(object sender, EventArgs e)
{
AA a = new AA ();
a.Dispose();
GC .Collect();
}
每次都既有“ dispose 执行了又有”“析构函数被执行了”,这意味着GC 又来捣乱了,哪么像这样包含Stream connection 的对象,就不用GC 来清理了,只需要我们加上最后一句话 GC.SuppressFinalize(this) 来告诉GC ,让它不用再调用对象的析构函数中。 那么改写后的AA 的dispose 方法如下:
public void Dispose()
{
fs.Dispose();
MessageBox .Show("dispose 执行了" );
GC.SuppressFinalize(this);
}
看了网上一博客关于垃圾回收文章:
http://blog.csdn.net/xmsheji/article/details/5452914
博主写的很清晰很好。里边写到:
“哪么在点击第一次按钮的时候,生成四个对象,然后强制垃圾回收,这个时候,会回收吗?当然不会,因为,这四个对象还在执行中(方法还没结束),当点第二次按钮的时候,会出现四次" 析构函数被执行了" , 这是在释放第一次点击按钮的四个对象,然后以后每次点击都会出现四次" 析构函数被执行了" ,哪么最后一次的对象什么时候释放的,在关闭程序的时候释放(因为关闭程序要释放所有的内存)。”
这里自己提出了个问题:点击第一次按钮等方法结束后可不可以马上对对象进行回收呢?
答案是可以的。
先说一下个人的理解,仅供参考:
GC是对没有引用的内存进行回收的,在方法中,内存中 a、b、c、d 所占用的内存还是有效的,在方法结束前虽然执行了
GC.Collect() 但是垃圾回收器判断这几个对象在当前方法结束前依然是有引用的,所以没有对他们进行回收。
直到第二次点击按钮,执行方法(注意这时候所创建的4个对象和第一次执行创建的4个对象是不同的),到执行到GC.Collect();的时候,
回收器对无用对象进行回收,这时候检测到的第一次创建的4个对象是不没有被调用的,所以回收了它们,输出4个“析构函
数被执行了“,而第二次创建的4个对象依然在内存中,和第一次创建的对象一样存在等待下一次执行回收的时候回收。
所以如果想马上对对象进行回收,可以给对象赋值 null. 然后执行GC.Collect() 即可!
我们来验证一下:
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int index = 1;
private void button1_Click(object sender, EventArgs e)
{
AA a = new AA();
a.id = "a" + index;
AA b = new AA();
b.id = "b" + index;
AA c = new AA();
c.id = "c" + index;
a = null; // 将引用设置为空 ,马上垃圾回收
GC.Collect();
b = null; // 将引用设置为空 ,这里不会马上回收,等待下一次执行GC.Collect()回收
index++;
}
}
public class AA
{
public string id = string.Empty;
~AA()
{
MessageBox.Show(id + " 析构函数被执行了");
}
}
}
运行:
第一次点击按钮 :
![]()
马上提示 a1 被回收了(因为 a1 赋值null,所占内存无效引用被认为是垃圾)。
而 b1 c1 都没有(因为它们在方法内依然是有效的。虽然 b1 = null 了但是他是在执行GC.Collect()回收之后的,所以没有马上回收等待下一次执行GC.Collect()回收)。
我们第二次点击按钮:
这里首先提示 c1 被回收了(为什么是c1呢,b1我们都赋值null啦,可能是栈吧,后进先出的原理,这里不研究╮(╯▽╰)╭)
我们在点确定:
![]()
这里 b1 被回收了
我们在点确定:
这里 a2 被回收了 (这里a2 赋值 null了,所以执行GC.Collect()被回收)
我们在点确定下去的顺序就是 c2 b2 a3 ..........
我们关闭的时候顺序是:c3 b3 没有a3
结论:
执行GC.Collect()并不会马上回收当前方法内GC.Collect()之前创建的对象
需要立刻马上对对象进行垃圾回收的,可以先给对象赋值null,然后执行GC.Collect()进行垃圾回收