建议52:及时释放资源

垃圾回收机制自动为我们隐式地回收了资源(垃圾回收器会自动调用终结器),那我们为什么要主动释放资源呢?

private void buttonOpen_Click(object sender,EventArgs e)
{
  FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open);
} private void buttonGC_Click(object sender,EventArgs e)
{
  System.GC.Collect();
}

这是一个WinForm窗体程序的例子,在这个示例中,单击一个按钮负责打开一个文件,单击另一个按钮负责回收说有“代”(代的概念会在下文详细指出)的垃圾。如果连续两次单击打开文件的按钮,系统就会报错:

IOException:文件“c:\test.txt”正由另外一进程使用,因此该进程无法访问此文件。

如果先单击打开文件的按钮,继而再单击清理按钮,则运行正常。

现在来分析:在打开文件的方法中,方法执行完毕后,由于局部变量fileStream在程序中已经没有任何地方引用了,所以它会在下一次垃圾回收时被运行时标记为垃圾。那么,什么时候会进行下一次垃圾回收呢,后者说垃圾回收器什么时候才开始真正进行回收工作呢?微软官方的解释是,当满足以下条件之一时将发生垃圾回收:

  • 系统具有低的物理内存。
  • 由托管堆上已分配的对象使用的内存超出了可接受的阈值。 这意味着可接受的内存使用的阈值已超过托管堆。 随着进程的运行,此阈值会不断地进行调整。
  • 调用 GC.Collect 方法。 几乎在所有情况下,您都不必调用此方法,因为垃圾回收器会持续运行。 此方法主要用于特殊情况和测试。

不及时释放资源是对系统的一种极大的浪费,这种浪费还会干扰程序的正常运行(如在本实例中,由于它始终占着文件资源,导致我们不能再次使用这个文件资源了)。

若果类型本身继承了IDisposable接口,垃圾回收机制虽然会自动帮我们释放资源,但是这个过程却延长了,因为它不是在一次回收中完成所有的清理工作。本例中因为fileStream继承了IDisposable接口,故第一次进行垃圾回收时,垃圾回收器会调用fileStream的终结器,然后等待下一次的垃圾回收,这时fileStream对象才有可能被真正的回收掉。

我们来改进这个程序:

private void buttonOpen_Click(object sender,EventArgs e)
{
FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open);
fileStream.Dispose();
}

但是如果第一行代码出现异常,就永远执行不了filsStream.Dispose()了。再次改进:

private void buttonOpen_Click(object sender,EventArgs e)
{
try
{
FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open);
}
finally
{
fileStream.Dispose();
}
}

使用语法糖“using"关键字进一步简化:

private void buttonOpen_Click(object sender,EventArgs e)
{
using(FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open))
{ }
}

关于“代数”:一共分为3代:第0代、第1代、第2代。

  • 第 0 代:这是最年轻的代,其中包含短生存期对象。 短生存期对象的一个示例是临时变量。 垃圾回收最常发生在此代中。新分配的对象构成新一代的对象并且为隐式的第 0 代回收,除非它们是大对象,在这种情况下,它们将进入第 2 代回收中的大对象堆。大多数对象通过第 0 代中的垃圾回收进行回收,不会保留到下一代。
  • 第 1 代:这一代包含短生存期对象并用作短生存期对象和长生存期对象之间的缓冲区。
  • 第 2 代:这一代包含长生存期对象。 长生存期对象的一个示例是服务器应用程序中的一个包含在进程期间处于活动状态的静态数据的对象。

当条件得到满足时,垃圾回收将在特定代上发生。 回收某个代意味着回收此代中的对象及其所有更年轻的代。 第 2 代垃圾回收也称为完整垃圾回收,因为它回收所有代上的所有对象(即,托管堆中的所有对象)。

幸存和提升

垃圾回收中未回收的对象也称为幸存者,并会被提升到下一代。 在第 0 代垃圾回收中幸存的对象将被提升到第 1 代;在第 1 代垃圾回收中幸存的对象将被提升到第 2 代;而在第 2 代垃圾回收中幸存的对象将仍为第 2 代。

当垃圾回收器检测到某个代中的幸存率很高时,它会增加该代的分配阈值,因此下一次回收将会获取一个非常大的回收内存。 CLR 会在以下两个优先级别之前进行平衡:不允许应用程序的工作集获取太大内存以及不允许垃圾回收花费太多时间。

暂时代和暂时段

因为第 0 代和第 1 代中的对象的生存期较短,因此,这些代被称为暂时代。

暂时代必须在称为暂时段的内存段中进行分配。 垃圾回收器获取的每个新段将成为新的暂时段,并包含在第 0 代垃圾回收中幸存的对象。 旧的暂时段将成为新的第 2 代段。

更多内容可参考MSDN:垃圾回收的基础

转自:《编写高质量代码改善C#程序的157个建议》陆敏技

编写高质量代码改善C#程序的157个建议——建议52:及时释放资源的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  2. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  3. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  4. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  5. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  6. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  7. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  8. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

  9. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

  10. 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法

    建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...

随机推荐

  1. art-template前端高性能模板

    template.js资源 /*!art-template - Template Engine | http://aui.github.com/artTemplate/*/!function(){fu ...

  2. java代码从键盘输入次数,然后进行运算-----菜鸟如此菜

    package com.aaa; import java.util.Scanner; // //求两数相加的和.从键盘输入 public class ftwert { public static vo ...

  3. java数字游戏基础篇

    题目: 1.100以内,程序先产生一个随机数,然后用户从控制台输入数字,猜是大了还是小了,第7次时 输出 “您太笨了,答案是”+x 思路:1.随机数,所以用到random 2.用户从键盘输入 ,则用到 ...

  4. Vue源码学习(一):调试环境搭建

    最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...

  5. python 通过ftplib 实现上传下载

    #!/usr/bin/python #-*- coding: utf-8 -*- from ftplib import FTP def ftpconnect() ftp_server = 'ftp.p ...

  6. table td 实现细边框的样式

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org ...

  7. Java面向对象-方法的定义及简单使用

    Java面向对象之方法 方法是对象的一部分,也称为行为: 先来一个简单实例: package com.java1234.chap03.sec03; public class Person { void ...

  8. Java中 单例(Singleton)的两种方式

    第一种(饿汉式单例模式):在声明变量时实例化 public class Singleton { //静态初始化自动实例化 private static Singleton instance = new ...

  9. ffmpeg截取一段视频中一段视频

    ffmpeg  -i ./plutopr.mp4 -vcodec copy -acodec copy -ss 00:00:10 -to 00:00:15 ./cutout1.mp4 -y -ss ti ...

  10. Enumeration & Class & Structure

    [Enumeration] 1.当一个枚举值类型已经确定后,可以使用shorter dot syntax来赋予其它值: 2.对一个枚举值switch的时候也可以使用short dot syntax: ...