建议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个建议》陆敏技