.Net Discovery系列之四 深入理解.Net垃圾收集机制(下)

上一节给大家介绍了 .Net GC的运行机制,下面来讲下与GC相关的重要方法。

第二节.GC关键方法解析

1.Dispose()方法

Dispose可用于释放所有资源,包括托管的和非托管的,需要自己实现。

大多数的非托管资源都要求手动释放,我们应当为释放非托管资源公开一个方法,实现释放非托管资源的方法有很多种,实现IDispose接口的Dispose方法是最好的,这可以给使用你类库的程序员以明确的说明,让他们知道怎样释放你的资源;而且C#中用到的using语句快,也是在离开语句块时自动调用Dispose方法。

这里需要注意的是,如果基类实现了IDispose接口,那么它的派生类也必须实现自己的IDispose,并在其Dispose方法中调用基类中Dispose方法。只有这样的才能保证当你使用派生类实例后,释放资源时,连同基类中的非托管资源一起释放掉。

插曲:使用using与try+finally的区别

可以说2者没有任何区别,因为using只是编辑器级的优化,它与try+finally有着相同的作用,以下是一段使用using的代码,它在IL阶段也是以try+finally呈现的:

C#:

public partial class _Default : System.Web.UI.Page
    {
     protected void Page_Load(object sender, EventArgs e) 
     {
       using (DataSet ds = new DataSet())
       {
        }
     }
   }

   MSIL:
   .method family hidebysig instance void  Page_Load(object sender,class [mscorlib]System.EventArgs e) cil managed
   {
    // 代码大小       29 (0x1d)
    .maxstack  2
    .locals init ([0] class [System.Data]System.Data.DataSet ds,
             [1] bool CS$4$0000)
    IL_0000:  nop
    IL_0001:  newobj     instance void [System.Data]System.Data.DataSet::.ctor()
    IL_0006:  stloc.0
    .try
    {
      IL_0007:  nop
      IL_0008:  nop
      IL_0009:  leave.s    IL_001b
    }  // end .try
    finally
    {
      IL_000b:  ldloc.0
      IL_000c:  ldnull
      IL_000d:  ceq
      IL_000f:  stloc.1
      IL_0010:  ldloc.1
      IL_0011:  brtrue.s   IL_001a
      IL_0013:  ldloc.0
      IL_0014:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
      IL_0019:  nop
      IL_001a:  endfinally
    }  // end handler
    IL_001b:  nop
    IL_001c:  ret
   } // end of method _Default::Page_Load

但是,using的优点是,在代码离开using块时,using会自动调用Idispose接口的Dispose()方法。

 

2. GC.Collect()方法

如果我们在程序中显式的调用了垃圾收集器的collect接口,那么垃圾收集器会立即运行,完成内存对象的标记、压缩与清除工作,使用GC.Collect(i)还可以指定回收的代,然而aicken并不赞成各位同学显式调用它:

⑴. GC.Collect()做的并不只是回收内存,就像第一节中介绍的,在回收了内存之后,GC会重新整理内存,修正对象指针,让空闲内存连续,供CLR顺序分配内存,提高新建对象的效率。内存压缩整理工作非常耗用计算资源。

⑵.很少有人会关心到GC除了在内存吃紧以及资源空闲时运行,还会在什么时候运行。 其实GC的运行时机,还要受到一个叫做“策略引擎”的部件控制,它会观察GC的收集频率、效率等等。它会根据GC回收效果,调整GC运行的频率:即当某次GC回收效果颇丰时,它便会增加GC运行的频率,反之亦然。

所以如果刚刚发生了一次自然的收集,垃圾对象就会非常之少,而此时程序又显式的进行了收集调用,那么自然, GC虽然小有收获,但是策略引擎就会认为:这很不值得,才收集了这么点垃圾,也许该减少GC的次数。这样一来,垃圾收集器努力保持的自然节奏就被打乱了。

同时,对象类型的创建效率与频率,也会被“策略引擎”捕捉到,从而改变代的数量与容量。

所以,额外的调用GC,代价高昂,甚至会降低效率。显示的调用GC.Collect(),实质是在用“时间换空间”,而通常在程序设计中,我们推荐的设计原则是“空间换时间”,比如使用各种各样的缓存。

也有例外,如果你掌握了整个应用程序的情况,明确的知道何时会产生大量垃圾,也是可以显示调用该方法的。

综上,尽量不要显示调用GC.Collect(),因为服务器的CPU比内存要贵的多!

3. 析构函数(Finalize())

我们知道,GC只负责释放托管资源,非托管资源GC是无法释放的。类似文件操作、数据库连接等都会产用非托管资源。

Finalize方法是用于释放非托管资源的,等同于C#中是析构函数,C#编译器在编译构造函数时,会隐式的将析构函数编译为Finalize()对应的代码,并确定在finally块中执行了base.Finalize()。

析构函数中只能释放非托管资源,而不要在任何托管资源进行析构,原因如下:

⑴你无法预测析构函数的运行时机,它不是按顺序执行的。当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。

⑵包含Finalize()的对象,需要GC的两次处理才能删除。

⑶CLR会在单独的线程上执行所有对象的Finalize()方法,无疑,如果频繁的Finalize(),会降低系统的性能。

下面我们来重点说说第⑵点,为何包含Finalize()的对象,需要两次GC才能被清除。

首先要了解与Finalize相关的两个队列:终止队列(Finalization Queue)与可达队列(Freachable Queue),这两个队列存储了一组指向对象的指针。

当程序中在托管堆上分配空间时(new),如果该类含有析构函数,GC将在Finalization Queue中添加一个指向该对象的指针。

在GC首次运行时,会在已经被确认为垃圾的对象中遍历,如果某个垃圾对象的指针被Finalization Queue包含,GC将这个对象从垃圾中分离出来,将它的指针储存到Freachable Queue中,并在Finalization Queue删除这个对象的指针记录,这时该对象就不是垃圾了——这个过程被称为是对象的复生(Resurrection)。当Freachable Queue一旦被添加了指针之后,它就会去执行对象的Finalize()方法,清除对象占用的资源。

当GC再次运行时,便会再次发现这个含有Finalize()方法的垃圾对象,但此时它在Finalization Queue中已经没有记录了(GC首次运行时删掉了它的Finalization Queue记录),那么这个对象就会被回收了。

至此,通过GC两次运行,终于回收了带有析构函数的对象。

 

复活实例:

private void Form1_Load(object sender, EventArgs e) 
   {
     Resource re = new Resource();
     re = null;GC.Collect();
     GC.WaitForPendingFinalizers();
     //首次GC.Collect()没起作用哦。 
     label1.Text = re.num.ToString();
   }
   public class Resource
  {
   public int num;
     ~Resource()
     {
       。。。
     }
   }

看了上面的代码,大家应该了解什么是复活了吧!那么为什么要复生呢?因为首次GC时,这个对象的Finalize()方法还没有被执行,如果不经过复生就被GC掉,那么就连它的Finalize()一起回收了,Finalize()就无法运行了,所以必须先复生,以执行它的Finalize(),然后再回收。

还有两个方法ReRegisterForFinalize和SuppressFinalize需要讲一讲,ReRegisterForFinalize是将指向对象的指针重新添加到Finalization Queue中(即召唤系统执行Finalize()方法),SuppressFinalize是将对象的指针从Finalization Queue中移除(即拒绝系统执行Finalize()方法)。

SuppressFinalize用于那些即有析构函数来释放资源,又实现了Dispose()方法释放资源的情况下:将GC.SuppressFinalize(this)添加至Dispose()方法中,以确保程序员调用Dispose()后,GC就不必再次收集了,例如以下代码:

public class Resource : Idisposable
   {
      private bool isDispose = false;
      //实现Dispose(),后面还有析构函数,以防程序员忘记调用Dispose()方法
      public void Dispose() {
      Dispose(true);
       GC.SuppressFinalize(this);
    }
   protected virtual void Dispose(bool disposing)
   {
    if (!isDispose)
    {
     if (disposing)
     {
      //清理托管资源
     }
     //清理非管资源
    }
    isDispose = true;
   }
   ~ Resource ()
   {
    Dispose(false);
   }
  }

即实现Idisposable中的Dispose()方法,又使用析构函数,一个双保险,大家不要迷惑,其实在释放非托管资源时,使用一个即可,推荐使用前者。

4.弱引用(WeakReference)

最后一个话题:弱引用。在编程中,对于那些大对象建议使用这种引用方式,这种引用不影响GC回收:我们用过了某个对象,然后将其至null,这样GC就可以快速回收它了,但是没过多久我们又需要这个对象了,没办法,只好重新创建实例,这样就浪费了创建实例所需的计算资源;而如果不至null,就会浪费内存资源。对于这种情况,我们可以创建一个这个大对象的弱引用,这样在内存不够时GC可以快速回收,而在没有被GC回收前我们还可以再次利用该对象。

public class SomeObject 
   {
     。。。
   }

   public static void Main() 
   {
      SomeObject so = new SomeObject();
      WeakReference WRso = new WeakReference(so);
     so = null;
      Console.WriteLine(WRso.IsAlive); // True
      // 调用GC 手动回收。
      GC.Collect();
      Console.WriteLine(WRso.IsAlive); // False

   }

看到没,在so = null;后,它的弱引用依然是可用的。所以对于大对象的使用,aicken建议使用此种方式。另外,弱引用有长短之分:长弱引用在对象终结后,依然追踪对象;短弱引用则反之,aicken不建议人为干预GC的工作成果,所以推荐使用短弱引用,即上面代码中的方式。

通过以上的讲解,相信大家已经能够很全面的了解.Net GC方面的知识了。

 

转自:http://www.cnblogs.com/isline/archive/2009/03/04/1402713.html

转载于:https://www.cnblogs.com/zjoch/p/5237455.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/255848.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

真静态和伪静态的区别

首先肯定的是纯静态和伪静态都是SEO的产物,但纯静态和伪静态还是有很大区别的。 纯静态是生成真实的HTML页面保存到服务器端,用户访问时直接访问这 个HTML页面即可,从而大大的减轻了服务器压力(如dedecms就是采用的纯静态&#xf…

非常有趣的Console

console觉醒之路,打印个动画如何? 原文地址: http://www.helloweba.com/view-blog-383.html 批量去掉或替换文本中的换行符(notepad、sublime text2) 原文地址:http://m.blog.csdn.net/article/details?id43228729 有…

shopee虾皮科技测试工程师第一次笔试

10道单选题 10道多选题 2道编程题 第一题:十进制转二进制计算1的个数(负数转为补码) #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2021/8/23 15:44 # Author : linlianqin # Site : # File : 十进制转换为二进制&am…

假期实践

第一天 地点:杭州颐高数码城 第一天,我来到了自己家附近的颐高数码城。文三路这边有一个卖数码产品的一条街,这里也是最贴近我专业实践的地方,所以第一天的实践我选择了这里。 2001年开业的颐高数码广场座落于“电子一条街”文三路、学院路口…

3.AngularJS-过滤器

转自:https://www.cnblogs.com/best/p/6225621.html 二、过滤器 使用过滤器格式化数据,变换数据格式,在模板中使用一个插值变量。语法格式如下: {{ express | filter:parameter1:p2:p3… | … | …}} 过滤器分了内置过滤器与自定义…

webstorm卡顿问题

解决webstorm卡顿问题 webstorm强大的功能就不多做介绍了。但是它的缺点也显而易见:吃内存。 电脑配置稍低一点,运行webstorm就特别容易卡顿,特别是项目比较大的时候,那卡顿得不要不要的。 在我的笔记本8g内存 256ssd的配置下&…

cmd.exe启动参数说明

启动命令解释程序 Cmd.exe 的新范例。如果在不含参数的情况下使用,cmd 将显示操作系统的版本和版权信息。 语法 cmd [{/c | /k}] [/s] [/q] [/d] [{/a | /u}] [/t:FG] [/e:{on | off}] [/f:{on | off}] [/v:{on | off}] [String] 参数 /c 执行 String 指定的命令&am…

【深度学习】——训练过程

包含哪些层 训练过程 其实就是yf(x)的求参过程,先给参数一个初始值,然后根据初始函数计算得到预测值,根据预测值和真值计算损失,然后又根据损失函数进行反向传播更新参数,更新参数后,再次计算预测值&#…

ABB RAPID 程序 WorldZone 归纳

在 RAPID 程序中,静态的 WorldZone 不能被解除并再次激活,或者进行擦除。在 RAPID 程序中, 临时的 WorldZone 可以被解除(WZDisable) , 再次激活(WZEnable) 或者擦除(WZF…

thinkphp自定义模板标签(一)

thinkphp内置的foreach和include等模板标签使用是非常方便的;但是内置的那些标签只能满足常用功能,个性化的功能就需要我们自己编写自定义模板标签了;下面就是要讲解如何实现; 示例环境:thinkphp3.2.3 thinkphp的模板标…

【深度学习】——激活函数(sigmoid、tanh、relu、softmax)

目录 激活函数 1、作用 2、常用激活函数 3、衡量激活函数好坏的标准: 4、不同的激活函数 1)sigmoid 2)tanh函数 3)RULE函数和leak-relu函数 4)softmax函数 激活函数 1、作用 如果只是线性卷积的话&#xff0c…

SDUT 3377 数据结构实验之查找五:平方之哈希表

数据结构实验之查找五:平方之哈希表 Time Limit: 400MS Memory Limit: 65536KBSubmit StatisticProblem Description 给定的一组无重复数据的正整数,根据给定的哈希函数建立其对应hash表,哈希函数是H(Key)Key%P,P是哈希表表长&…

我的2017年前端之路总结

原文首发于我的博客 年末了,赶着刚考完两门考试,在最后4门考试来临之前抽空写一下今年的小结。 今年格外忙。忙完本科毕设,又马上投入了研究生实验室的搬砖生涯。跟去年一样,列个今年的学习成果清单: 过去的一年 技术成…

对软件工程的疑问

在大学时光中学习了算法编程后,我发现我对于源程序理解很差,我只会很低程度的写代码,但是基本描述不出来。所以我的编程很差,而且由于我很少打代码,所以我的编程能力基本没有多少提高,我也没有发现该学什么…

【深度学习】——分类损失函数、回归损失函数、交叉熵损失函数、均方差损失函数、损失函数曲线、

目录 代码 回归问题的损失函数 分类问题的损失函数 1、 0-1损失 (zero-one loss) 2、Logistic loss 3、Hinge loss 4、指数损失(Exponential loss) 机器学习的损失函数 Cross Entropy Loss Function(交叉熵损失函数) 交叉熵优点 Mean Squared E…

伺服电机惯量问题

在伺服系统选型及调试中,常会碰到惯量问题。 其具体表现为:在伺服系统选型时,除考虑电机的扭矩和额定速度等等因素外,我们还需要先计算得知机械系统换算到电机轴的惯量,再根据机械的实际动作要求及加工件质量要求来…

【转】应用架构一团糟?如何将单体应用改造为微服务

概述 将单体应用改造为微服务实际上是应用现代化的过程,这是开发者们在过去十年来一直在做的事情,所以已经有一些可以复用的经验。 全部重写是绝对不能用的策略,除非你要集中精力从头构建一个基于微服务的应用。虽然听起来很有吸引力&#xf…

Linux 解决ssh连接慢的问题

备份文件 cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak 编辑文件 vi /etc/ssh/sshd_config 输入/ 查找GSSAPIAuthentication 设置如下 GSSAPIAuthentication no # 是否允许使用基于 GSSAPI 的用户认证。默认值为"no"。仅用于SSH-2 详细解释 输入/ 查找UseDNS …

ABB机器人与PC计算机控制口连接 超级终端 命令清单

条件: 9 针串口通信 RS232。 PC 启动超级终端软件。Windows -> Start -> Accessories -> Terminal 通信设置: 1. 波特率 9600 8 位2. 1 个停止位 没有奇偶校验3. 没有 Modern 采用直接串口连接4. 使用 Xon/Xoff 通信形式当故障发生时&#xff0…

【Hibernate】Hibernate系列6之HQL查询

HQL查询 6.1、概述 6.2、分页查询 6.3、命名查询 6.4、投影查询-部分字段查询 6.5、报表查询 6.6、迫切左外连接、左外连接 6.7、迫切内连接、内连接 6.8、QBC查询、本地查询 转载于:https://www.cnblogs.com/junneyang/p/5254641.html