HTML5技术

谈一谈.net析构函数对垃圾回收的影响 - 苦逼师哥

字号+ 作者:H5之家 来源:H5之家 2016-05-24 16:00 我要评论( )

之前忘了说了 代码都是在Release模式下运行的,现在补充上。 这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassName)的形式,但是功能上完全不一样。析构函数编译成il语言后会变成一个Finalize的函

之前忘了说了 代码都是在Release模式下运行的,现在补充上。

这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassName)的形式,但是功能上完全不一样。析构函数编译成il语言后会变成一个Finalize的函数,他是重写的object的Finalize虚函数,标题上用析构函数,主要是我认为很多人不知道Finalize函数。
写一个类型解释下可能会更通俗易懂一点:

public class Test { ~Test() { } [] b = new byte[10000]; }

 

  最近看了一些代码,有不少用Finalize函数的。特别是ef数据仓库中,情况如下:

public class DbRepostory { private Context context; public DbRepostoty(Context context) { this.context = context; } ~DbRepostory() { context.Dispose(); } } public class Context : DbContext { }

  看上去很高大上,但是这样写到底好不好呢?好不好我们最后再去评论,先看一看下面这个简单的例子:

public class WithFinalize { ~WithFinalize() { } [10000]; } public class WithoutFinalize { [10000]; } class Program { Main(string[] args) { Console.WriteLine(); Test<WithoutFinalize>(); Console.WriteLine(Environment.NewLine+ ); Test<WithFinalize>(); Console.ReadKey(); } Test<T>() where T : new() { GC.Collect(); Thread.Sleep(10); Console.WriteLine(+ GC.GetTotalMemory(false)); var list = new List<T>(); for (int i = 0; i < 10; i++) list.Add(new T()); Console.WriteLine(+ GC.GetTotalMemory(false)); GC.Collect(); Thread.Sleep(10); Console.WriteLine(+ GC.GetTotalMemory(false)); GC.Collect(); Thread.Sleep(10); Console.WriteLine(+ GC.GetTotalMemory(false)); } }

这段代码有三个类一个是我们需要运行的主程序,另外两个 WhitFinalize 和WhitoutFinalize则是我们要测试的类型,这两个类一个加了Finalize函数,一个未加,其余的完全一样。主程序则分别要测试这两个类型在垃圾回收的时的表现,我们先测试的没有加Finalize函数的类型,在测试的加了类型。 一共四个数值,分别是初始时的内存, new了10个测试类型之后的内存(测试类型大约需要10k的内存空间,10个也就是大约100k),垃圾回收一次之后的内存,垃圾回收二次之后的内存,我们看下具体的运行情况:

测试1无Finalize函数: 初始内存:96224 分配之后:196464 一次回收:97036 二次回收:97036 测试2有Finalize函数: 初始内存:97056 分配之后:197296 一次回收:197396 二次回收:97156

从运行情况来看两次测试的初始化内存都大约97k左右,new了10个测试对象之后都增长了大约100k,和预期的一样,但是第一次垃圾回收之后测试1(没有Finalize函数)回收了100k左右的内存,而测试2(有Finalize函数)则基本上没有回收掉内存,却等到了第二次垃圾回收 回收了100k内存。不禁会想,这又是为什呢?

这得从垃圾回收的一些原理说起,东西比较多,我们说的简单一下。垃圾回收的时候会从根遍历所有引用的对象,然后遍历到了就做好标记,代表有用,没遍历到的就会是为垃圾,但是在这些垃圾中有一些对象定义了Finalize函数,于是就把这些有Finalize的对象从垃圾堆里拉了回来,其余的垃圾则回收掉,而这些死而复活的对象则和那些本来就不是垃圾对象都幸存了下来,并一并升级为下一代对象,垃圾回收结束之后 clr会用一个较高优先级的线程来调用这些死而复活对象的Finalize方法,直到下次垃圾回收他们才被回收掉。这也是我们看到测试2第二次垃圾回收才被回收掉的原因,我们在这里讲的都是一些粗略的东西,内部实现还要复杂。


我们看到我在代码里用到了很多Thread.Sleep(10); 这是什么原因呢?这就的注意下我上一段的一句话“垃圾回收结束之后 clr会用一个较高优先级的线程来调用这些死而复活对象的Finalize方法”,Finalize方法的调用和我们的前台代码是并发进行的,而且我们前台代码比较简单,如果不暂停一下的话很可能不少对象的Finalize方法还没执行完,我们就调用了下一次的垃圾回收(GC.Collect())。影响结果的准确性。

还有我们之前提到了代的概念,这里也简单说一下代,垃圾回收时对象一共有三代 :0,1,2。每一代都有自己的内存预算,空间不足的时候会调用垃圾回收。为了提高性能都是按代回收,第0代超预算之后就回收第0代的对象,而存活下来的对象就提升为第1代,依次类推,而往往经过多次0代的垃圾回收才能回收一次第1代。

我们代码中的GC.Collect();没有参数,意思是回收所有代的对象,我们可以把GC.Collect()换成GC.Collect(0);意思是回收第0代的对象,然后运行程序:

Test<T>() where T : new() { GC.Collect(); Thread.Sleep(10); Console.WriteLine(+ GC.GetTotalMemory(false)); var list = new List<T>(); for (int i = 0; i < 10; i++) list.Add(new T()); Console.WriteLine(+ GC.GetTotalMemory(false)); GC.Collect(0); Thread.Sleep(10); Console.WriteLine(+ GC.GetTotalMemory(false)); GC.Collect(0); Thread.Sleep(10); Console.WriteLine(+ GC.GetTotalMemory(false)); }

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • Dora.Interception: 一个为.NET Core度身定制的AOP框架 - Artech

    Dora.Interception: 一个为.NET Core度身定制的AOP框架 - Artech

    2017-05-02 11:00

  • 如何在 ASP.NET Core 中发送邮件 - Savorboard

    如何在 ASP.NET Core 中发送邮件 - Savorboard

    2017-05-02 08:02

  • 十二个 ASP.NET Core 例子 - Savorboard

    十二个 ASP.NET Core 例子 - Savorboard

    2017-04-27 16:01

  • ASP.NET MVC5请求管道和生命周期 - 雪飞鸿

    ASP.NET MVC5请求管道和生命周期 - 雪飞鸿

    2017-04-24 08:04

网友点评
t