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

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

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

    public class Test
{
~Test() { } //这个就是Finalize函数
private byte[] 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() { }
private byte[] b = new byte[10000];
}
public class WithoutFinalize
{
private byte[] b = new byte[10000];
}
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(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代的对象,然后运行程序:

        public static void 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无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析构函数对垃圾回收的影响

    之前忘了说了 代码都是在Release模式下运行的,现在补充上. 这里说析构函数,其实并不准确,应该叫Finalize函数,Finalize函数形式上和c++的析构函数很像 ,都是(~ClassNam ...

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

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

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

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

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

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

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

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

  6. KingbaseESV8R6垃圾回收受到参数old_snapshot_threshold的影响

    垃圾回收影响因素 影响垃圾回收的因素有很多,垃圾回收不及时,最直接导致表膨胀,详情查看文档<KingbaseESV8R6 垃圾回收原理以及如何预防膨胀>. vacuum回收垃圾的tuple ...

  7. C#内存管理和垃圾回收机制

    数据类型 垃圾回收机制 一.数据类型 C#中的数据类型分为值类型 (Value type) 和引用类型(reference type), 值  类 型: 所有的值类型都集成自 System.Value ...

  8. 管理Java垃圾回收的五个建议

    [编者按]本文作者是Niv Steingarten,是Takipi 的联合创始人,热衷于编写优雅简洁的代码.作者通过对垃圾收集器的介绍和梳理,在管理垃圾回收方面提出了五个建议,降低收集器开销,帮助大家 ...

  9. 降低Java垃圾回收开销的5条建议

    保持GC低开销的窍门有哪些? 随着一再拖延而即将发布的 Java9,G1(“Garbage First”)垃圾回收器将被成为 HotSpot 虚拟机默认的垃圾回收器.从 serial 垃圾回收器到CM ...

随机推荐

  1. HTTP 301 跳转和302跳转的区别

    常用的重定向方式有: 301 redirect, 302 redirect 与 meta fresh: 301 redirect::301代表永久性转移(Permanently Moved),301重 ...

  2. 基于visual Studio2013解决算法导论之054图的邻接矩阵表示

     题目 图的邻接矩阵表示 解决代码及点评 // 图的邻接矩阵表示.cpp : 定义控制台应用程序的入口点. // #include <iostream> #include <l ...

  3. 走进C++程序世界-----函数相关(全局变量)

    全局变量 在函数外面定义的变量的作用域为全局,在程序的任何函数中都可用.与全局变量同名的局部变量不会修改全局变量的值,但会隐藏它.如果函数中有一个与全局变量同 名的局部变量时,则在函数中使用该名称时, ...

  4. hdu1540之线段树单点更新+区间合并

    Tunnel Warfare Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) T ...

  5. MIT 三课程

    mit三课程: Introduction to Computer Science and Programming artificial intelligence introduction to alg ...

  6. BZOJ 1009: [HNOI2008]GT考试( dp + 矩阵快速幂 + kmp )

    写了一个早上...就因为把长度为m的也算进去了... dp(i, j)表示准考证号前i个字符匹配了不吉利数字前j个的方案数. kmp预处理, 然后对于j进行枚举, 对数字0~9也枚举算出f(i, j) ...

  7. 一口一口吃掉Hibernate(八)——Hibernate中inverse的用法

    一.Inverse是hibernate双向关系中的基本概念.inverse的真正作用就是指定由哪一方来维护之间的关联关系.当一方中指定了“inverse=false”(默认),那么那一方就有责任负责之 ...

  8. oracle 表复制

    1. 复制表结构及其数据: create table table_name_new as select * from table_name_old 2. 只复制表结构: ; 或者 create tab ...

  9. ios开发问题:添加库和复制其它工程文件编译错误问题

    首先添加库问题: 选择页签Build Phases->Link Binary With Libraries点+添加库 然后command+shift+k清除历史debug的垃圾 复制其它工程文件 ...

  10. cocos2d-x3.2中加入Android手机震动

    本人宣布从此博文发出后,我的cocos2dx的引擎从cocos2dx3.1.1跳到cocos2dx3.2,哈哈,事实上变化不大的,不碍事~~~ 以下来说说在cocos中加入Android手机震动的功能 ...