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

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

    public class Test
{
~Test() { } //这个就是Finalize函数
private byte[] b = new byte[];
}

最近看了一些代码,有不少用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() { }
private byte[] b = new byte[];
}
public class WithoutFinalize
{
private byte[] b = new byte[];
}
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("测试1无Finalize函数:");
Test<WithoutFinalize>();
Console.WriteLine(Environment.NewLine+ "测试2有Finalize函数:");
Test<WithFinalize>(); Console.ReadKey();
}
public static void Test<T>() where T : new()
{
GC.Collect();
Thread.Sleep();
Console.WriteLine("初始内存:" + GC.GetTotalMemory(false));
var list = new List<T>();
for (int i = ; i < ; i++) list.Add(new T());
Console.WriteLine("分配之后:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
Console.WriteLine("一次回收:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
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代的对象,然后运行程序:

        public static void Test<T>() where T : new()
{
GC.Collect();
Thread.Sleep();
Console.WriteLine("初始内存:" + GC.GetTotalMemory(false));
var list = new List<T>();
for (int i = ; i < ; i++) list.Add(new T());
Console.WriteLine("分配之后:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
Console.WriteLine("一次回收:" + GC.GetTotalMemory(false));
GC.Collect();
Thread.Sleep();
Console.WriteLine("二次回收:" + GC.GetTotalMemory(false));
}
测试1无Finalize函数:
初始内存:96224
分配之后:196464
一次回收:97056
二次回收:97036 测试2有Finalize函数:
初始内存:97056
分配之后:197296
一次回收:197396
二次回收:197396

我们看到测试2中在第二次垃圾回收之后(对第0代)内存依旧没有回收掉,而这种情况更接近于实际。

从上面的小例子中我们了解到Finalize方法对性能和内存都有不好的影响,那为什么要存在这个方法呢?这里我们说一下要使用Finalize的两个情况:

第一个情况就是对象含有一个本机资源,比如一个句柄,这样可以在Finalize方法释放这个句柄,就能消除忘记释放句柄造成的本机资源浪费。

第二种情况就是在这个对象被回收之前需要做一些必须要做的是事情,比如FileStream这个类,需要在回收之前把缓冲区的东西写入到文件内。

我们在回过头开看一看之前提到的数据仓库的类,这个类第一没有占用任何本机资源,第二在被回收之前也没有必须要做的事情,写一个Finalize方法并调用 context.Dispose(); 只能增加性能开销,影响垃圾回收效果。我们可以用反编译软件看一下DbContext这个基类,他都没有Finalize方法,又何必再画蛇添足呢?

希望觉得对自己有帮助的朋友给我点个赞(●'◡'●)

谈一谈.net析构函数对垃圾回收的影响的更多相关文章

  1. net析构函数对垃圾回收的影响

    net析构函数对垃圾回收的影响 之前忘了说了 代码都是在Release模式下运行的,现在补充上. 这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数 ...

  2. .net析构函数对垃圾回收的影响简析

    这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassName)的形式,但是功能上完全不一样.析构函数编译成il语言后会变成一 ...

  3. 浅谈Chrome V8引擎中的垃圾回收机制

    垃圾回收器 JavaScript的垃圾回收器 JavaScript使用垃圾回收机制来自动管理内存.垃圾回收是一把双刃剑,其好处是可以大幅简化程序的内存管理代码,降低程序员的负担,减少因 长时间运转而带 ...

  4. C#析构函数与垃圾回收

    析构函数基本语法 C# class Car { ~ Car() // destructor { // cleanup statements... } } 析构函数说明 不能在结构中定义析构函数.只能对 ...

  5. PHP析构函数与垃圾回收

    析构函数:当某个对象成为垃圾或者当对象被显式销毁时执行. GC (Garbage Collector) 在PHP中,没有任何变量指向这个对象时,这个对象就成为垃圾.PHP会将其在内存中销毁.这是PHP ...

  6. 局部变量表中Slot复用对垃圾回收的影响详解

    看两段代码 1. package com.jvm; public class Test { public static void main(String[] args) { { byte[] plac ...

  7. 浅谈JVM垃圾回收

    JVM内存区域 要想搞懂啊垃圾回收机制,首先就要知道垃圾回收主要回收的是哪些数据,这些数据主要在哪一块区域. Java8和Java8之前的相同点有很多. 都有虚拟机栈,本地方法栈,程序计数器,这三个是 ...

  8. AJPFX浅谈Java 性能优化之垃圾回收(GC)

    ★JVM 的内存空间 在 Java 虚拟机规范中,提及了如下几种类型的内存空间: ◇栈内存(Stack):每个线程私有的.◇堆内存(Heap):所有线程公用的.◇方法区(Method Area):有点 ...

  9. 【深入Java虚拟机】之二:Java垃圾回收机制

    [深入Java虚拟机]之:Java垃圾收集机制 对象引用 Java中的垃圾回收一般是在Java堆中进行,因为堆中几乎存放了Java中所有的对象实例.谈到Java堆中的垃圾回收,自然要谈到引用.在JDK ...

随机推荐

  1. C#中使用Socket请求Web服务器过程

    最开始我们需要明白一件事情,因为这是这篇文章的前提: HTTP协议只是一个应用层协议,它底层是通过TCP进行传输数据的.因此,浏览器访问Web服务器的过程必须先有“连接建立”的发生. 而有人或许会问: ...

  2. 先有 Mac 还是先有银元?

    自从开设了这个公众号以后,收到最多的问题是:听了 Mac 君的介绍,我也觉得 Mac 很好,可是穷屌丝一枚,真买不起,怎么破?前几天我在微博上说,读了我的书,就有 Mac 了.居然有读者来问:这是真的 ...

  3. LNMP安装过程

    LNMP一键安装包是什么? LNMP一键安装包是一个用Linux Shell编写的可以为CentOS/RadHat/Fedora.Debian/Ubuntu/Raspbian/Deepin VPS或独 ...

  4. 一篇通俗易懂的CSS层叠顺序与层叠上下文研究

    网上有很多这方面的教程,但不是苦涩难懂就是从哪copy过来的,反正很长一段时间我是没看懂,时间长了也没打算去研究了,主要原因是,基本上很少会遇到那些问题(所以说啊,要是没有研究精神的才懒得管它).但自 ...

  5. 实战MEF(4):搜索范围

    在前面的文章中,几乎每个示例我们都会接触到扩展类的搜索位置,我们也不妨想一下,既然是自动扩展,它肯定会有一个或者多人可供查找的位置,不然MEF框架怎么知道哪里有扩展组件呢? 就像我们用导航系统去查找某 ...

  6. Java之控制反转和依赖注入

    1.简介 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性,下面通过一个例子来引入这一概念. 2.案例 1)一般情况下的类耦合 Main.java public clas ...

  7. 处理UnicodeDecodeError: ‘XXX' codec can't decode bytes in position...的问题

    错误信息: UnicodeDecodeError: ‘XXX' codec can't decode bytes in position 2-5: illegal multibyte sequence ...

  8. C#由变量捕获引起对闭包的思考

    前言 偶尔翻翻书籍看看原理性的东西确实有点枯燥,之前有看到园中有位园友说到3-6年工作经验的人应该了解的.NET知识,其中就有一点是关于C#中的闭包,其实早之前在看书时(之前根本不知道C#中还有闭包这 ...

  9. JS验证控件jQuery Validate

    jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求.该插件捆绑了一套有用的验证方法,包括 URL 和电子邮件验证 ...

  10. T-SQL:毕业生出门需知系列(六)

    第6课 用通配符进行过滤 6.1 LIKE 操作符 前面介绍的所有操作符都是针对已知值进行过滤.但是,这种过滤方法并不是任何时候都好用. 例如:怎样搜索产品名中包含文本 bean bag 的所有产品? ...