如何管好.net的内存(托管堆和垃圾回收)
一:C#标准Dispose模式的实现
需要明确一下C#程序(或者说.NET)中的资源。简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类:
托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象;
非托管资源:不受CLR管理的对象,windows内核对象,如文件、数据库连接、套接字、COM对象等;
毫无例外地,如果我们的类型使用到了非托管资源,或者需要显式释放的托管资源,那么,就需要让类型继承接口IDisposable。这相当于是告诉调用者,该类型是需要显式释放资源的,你需要调用我的Dispose方法。
不过,这一切并不这么简单,一个标准的继承了IDisposable接口的类型应该像下面这样去实现。这种实现我们称之为Dispose模式:
- publicclass SampleClass : IDisposable
- {
- //演示创建一个非托管资源
- private IntPtr nativeResource = Marshal.AllocHGlobal(100);
- //演示创建一个托管资源
- private AnotherResource managedResource =new AnotherResource();
- privatebool disposed =false;
- ///<summary>
- /// 实现IDisposable中的Dispose方法
- ///</summary>
- publicvoid Dispose()
- {
- //必须为true
- Dispose(true);
- //通知垃圾回收机制不再调用终结器(析构器)
- GC.SuppressFinalize(this);
- }
- ///<summary>
- /// 不是必要的,提供一个Close方法仅仅是为了更符合其他语言(如C++)的规范
- ///</summary>
- publicvoid Close()
- {
- Dispose();
- }
- ///<summary>
- /// 必须,以备程序员忘记了显式调用Dispose方法
- ///</summary>
- ~SampleClass()
- {
- //必须为false
- Dispose(false);
- }
- ///<summary>
- /// 非密封类修饰用protected virtual
- /// 密封类修饰用private
- ///</summary>
- ///<param name="disposing"></param>
- protectedvirtualvoid Dispose(bool disposing)
- {
- if (disposed)
- {
- return;
- }
- if (disposing)
- {
- // 清理托管资源
- if (managedResource !=null)
- {
- managedResource.Dispose();
- managedResource =null;
- }
- }
- // 清理非托管资源
- if (nativeResource != IntPtr.Zero)
- {
- Marshal.FreeHGlobal(nativeResource);
- nativeResource = IntPtr.Zero;
- }
- //让类型知道自己已经被释放
- disposed =true;
- }
- publicvoid SamplePublicMethod()
- {
- if (disposed)
- {
- thrownew ObjectDisposedException("SampleClass", "SampleClass is disposed");
- }
- //省略
- }
- }
在Dispose模式中,几乎每一行都有特殊的含义。
在标准的Dispose模式中,我们注意到一个以~开头的方法:
- ///<summary>
- /// 必须,以备程序员忘记了显式调用Dispose方法
- ///</summary>
- ~SampleClass()
- {
- //必须为false
- Dispose(false);
- }
这个方法叫做类型的终结器。提供终结器的全部意义在于:我们不能奢望类型的调用者肯定会主动调用Dispose方法,基于终结器会被垃圾回收器调用这个特点,终结器被用做资源释放的补救措施。
一个类型的Dispose方法应该允许被多次调用而不抛异常。鉴于这个原因,类型内部维护了一个私有的布尔型变量disposed:
private bool disposed = false;
在实际处理代码清理的方法中,加入了如下的判断语句:
- if (disposed)
- {
- return;
- }
- //省略清理部分的代码,并在方法的最后为disposed赋值为true
- disposed =true;
这意味着类型如果被清理过一次,则清理工作将不再进行。
应该注意到:在标准的Dispose模式中,真正实现IDisposable接口的Dispose方法,并没有实际的清理工作,它实际调用的是下面这个带布尔参数的受保护的虚方法:
- ///<summary>
- /// 非密封类修饰用protected virtual
- /// 密封类修饰用private
- ///</summary>
- ///<param name="disposing"></param>
- protectedvirtualvoid Dispose(bool disposing)
- {
- //省略代码
- }
之所以提供这样一个受保护的虚方法,是为了考虑到这个类型会被其他类继承的情况。如果类型存在一个子类,子类也许会实现自己的Dispose模式。受保护的虚方法用来提醒子类必须在实现自己的清理方法的时候注意到父类的清理工作,即子类需要在自己的释放方法中调用base.Dispose方法。
还有,我们应该已经注意到了真正撰写资源释放代码的那个虚方法是带有一个布尔参数的。之所以提供这个参数,是因为我们在资源释放时要区别对待托管资源和非托管资源。
在供调用者调用的显式释放资源的无参Dispose方法中,调用参数是true:
- publicvoid Dispose()
- {
- //必须为true
- Dispose(true);
- //其他省略
- }
这表明,这个时候代码要同时处理托管资源和非托管资源。
在供垃圾回收器调用的隐式清理资源的终结器中,调用参数是false:
- ~SampleClass()
- {
- //必须为false
- Dispose(false);
- }
这表明,隐式清理时,只要处理非托管资源就可以了。
那么,为什么要区别对待托管资源和非托管资源。在认真阐述这个问题之前,我们需要首先弄明白:托管资源需要手动清理吗?不妨先将C#中的类型分为两类,一类继承了IDisposable接口,一类则没有继承。前者,我们暂时称之为非普通类型,后者我们称之为普通类型。非普通类型因为包含非托管资源,所以它需要继承IDisposable接口,但是,这个包含非托管资源的类型本身,它是一个托管资源。所以说,托管资源需要手动清理吗?这个问题的答案是:托管资源中的普通类型,不需要手动清理,而非普通类型,是需要手动清理的(即调用Dispose方法)。
Dispose模式设计的思路基于:如果调用者显式调用了Dispose方法,那么类型就该按部就班为自己的所以资源全部释放掉。如果调用者忘记调用Dispose方法,那么类型就假定自己的所有托管资源(哪怕是那些上段中阐述的非普通类型)全部交给垃圾回收器去回收,而不进行手工清理。理解了这一点,我们就理解了为什么Dispose方法中,虚方法传入的参数是true,而终结器中,虚方法传入的参数是false。
二:如何管好.net的内存
.net的性能瓶颈,毫无疑问是在内存管理上面。自动内存回收解决不了所有的问题,反而会制造性能问题。所以大批c++专家都不赞同在c++内部添加类似.net的内存管理机制,只是有保留的通过程序库来支持相关技术。
java老爱说c/c++管不好内存,容易泄露。但是其实本质上还不是将本来该由终端程序员自己处理的事情,交给了框架开发人员来处理了。
既然都是程序员,凭什么说你这些框架开发者就不会出现人为错误?
他们是专家,对大部分菜鸟级别的程序员来说,这个策略有点帮助,这是当然。但是从根本上,交给框架开发人员处理是有局限性的。他们无法预料到最终的应用程序有怎样的需求,脱离了具体的开发环境,也就无法做出最体贴的解决方案出来。
因此,也不要认为.net之类的内存管理必然就很先进,有这么大的局限性存在,还敢号称什么“无泄漏”?
内存泄漏是怎样的概念?我想说说我的看法。
我认为内存如果不在它该释放的那一刻,到需要再度利用这段时间,做到平滑,就不能算是优秀的内存管理。
.net的管理机制是惰性的,非到迫不得已,都不会释放掉,结果就把内存无端端的占用掉了,系统可用内存无端端就少了一大截。
这种和泄漏也差不了多少,某种程度上甚至还更糟糕。比如你泄漏一整天,就几兆,无伤大雅,但是你却无端端占用几百兆的内存,这个对系统的性能影响更大。所谓的泄漏不泄漏,不是本质,本质是对系统的负担有多大,内存管理机制有多高效。
有没有办法,让我们对.net的内存管理更加主动?这就是这篇文章要探讨的内容。
在开发过程中,性能问题往往出现在滥用框架功能上面。比如string不适合拼接字符串,程序员却滥用了。诸如此类的,解决方案就是我们有能力去理解框架提供的内容和重新设计一个更加有效率的编码。为什么说我们不能忘掉算法这些,原因就在此,框架的功能并不能适用在特定需求上面的,如果离开框架我们就无法工作,那样做出来的产品往往就只是二三流水准。这些内容不需要很多,却又必不可少。
这就是控制力的问题,如何做到更加细致的控制,是关键时刻解决问题的制胜之道。在直路,赛车手和平民的区别也许相对来说不是很大,但是拐弯就体现真功夫。我们可能要花90%的力气去做这10%的工作,才能保证软件的品质。
如何提高我们对.net托管内存的控制?
一、理解.net的内存管理机制。
二、了解框架要求程序员负责的任务。
三、了解手动控制的手段。
以c#语言为例。
析构函数如: ~类名() 默认是不自动生成的,也不能继承。但是析构函数并不管理内存,.net把内存作为资源的独立部分来管理,内存自动管理,其余资源依赖程序员自己通过析构函数等手段来管理。
因为内存是自动管理的,程序员只需要了解第三点:手动控制的手段。
System.GC.Collect() 强制启动内存垃圾回收程序。
第二个问题是除内存之外的其余资源如何管理。
.net 把任何实现了析构函数的对象,加入一个待清理队列中,这个队列会去调用析构函数,这个时候就能释放资源了。
因此,只要实现了析构函数,然后把释放资源的代码放在析构函数内部,就能交由.net处理余下事项,你也不需要担心自己忘了清理。
如果我们想要他在立即执行这个操作,可以调用System.GC.Collect()来启动。
问题是,.net实现这个机制的代价是很高的,如果我们又要释放资源,又不想影响效率,而且又担心我们会忘了释放资源,可以采取以下设计模式:
1.实现一个释放资源的函数。方便我们手工调用。
2.在析构函数中调用这个函数。
3.释放资源的函数中调用System.GC.SupressFinalize(),就会把对象从.net的待清理队列中释放出来,这样.net就不会启动释放机制了。
4.必须有一个标志来判断是否已经把资源释放了,避免重复释放资源。
5.必须有一个标志来判断是否需要调用System.GC.SupressFinalize()。如果程序员忘了手动清理,而是.net自身通过调用析构函数去清理的,那把对象从待清理队列中释放已经是没有意义的事情了。
可见,如果要高效,还是免不了要程序员自己记得手工释放。
以上是管理内存和其余资源的方式,至于如何运用,我个人的看法是,只要资源没有利用价值了,就应该尽快释放掉,而不是等缓慢的.net回收机制。
本文文章转载自:
http://www.cnblogs.com/Nobel/archive/2010/12/19/1910626.html(改善C#程序的建议4:C#中标准Dispose模式的实现)
http://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html(如何管好.net的内存)
如何管好.net的内存(托管堆和垃圾回收)的更多相关文章
- cir from c# 托管堆和垃圾回收
1,托管堆基础 调用IL的newobj 为资源分配内存 初始化内存,设置其初始状态并使资源可用.类型的实列构造器负责设置初始化状态 访问类型的成员来使用资源 摧毁状态进行清理 释放内存//垃圾回收期负 ...
- 【C#进阶系列】21 托管堆和垃圾回收
托管堆基础 一般创建一个对象就是通过调用IL指令newobj分配内存,然后初始化内存,也就是实例构造器时做这个事. 然后在使用完对象后,摧毁资源的状态以进行清理,然后由垃圾回收器来释放内存. 托管堆除 ...
- .NET 托管堆和垃圾回收
托管堆基础 简述:每个程序都要使用这样或那样的资源,包括文件.内存缓冲区.屏幕空间.网络连接.....事实上,在面向对象的环境中,每个类型都代表可供程序使用的一种资源.要使用这些资源,必须为代表 ...
- 【CLR】解析CLR的托管堆和垃圾回收
目录结构: contents structure [+] 为什么使用托管堆 从托管堆中分配资源 托管堆中的垃圾回收 垃圾回收算法 代 垃圾回收模式 垃圾回收触发条件 强制垃圾回收 监视内存 对包装了本 ...
- 重温CLR(十五) 托管堆和垃圾回收
本章要讨论托管应用程序如何构造新对象,托管堆如何控制这些对象的生存期,以及如何回收这些对象的内存.简单地说,本章要解释clr中的垃圾回收期是如何工作的,还要解释相关的性能问题.另外,本章讨论了如何设计 ...
- CLR via C# 读书笔记-21.托管堆和垃圾回收
前言 近段时间工作需要用到了这块知识,遂加急补了一下基础,CLR中这一章节反复看了好多遍,得知一二,便记录下来,给自己做一个学习记录,也希望不对地方能够得到补充指点. 1,.托管代码和非托管代码的区别 ...
- 托管堆和垃圾回收(GC)
一.基础 首先,为了深入了解垃圾回收(GC),我们要了解一些基础知识: CLR:Common Language Runtime,即公共语言运行时,是一个可由多种面向CLR的编程语言使用的"运 ...
- C#托管堆和垃圾回收
垃圾回收 值类型 每次使用都有对应新的线程栈 用完自动释放 引用类型 全局公用一个堆 因此需要垃圾回收 操作系统 内存是链式分配 CLR 内存连续分配(数组) 要求所有对象从 托管堆分配 GC 触发条 ...
- NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配
在" .NET的堆和栈01,基本概念.值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配.我们知道:当执行一个方法的时 ...
随机推荐
- 三分钟掌握 JUnit3.0
曾经公司做过一个.net的项目,在项目开发的过程中.我们採用的是分层的开发方式,大家先在一起讨论接口, 然后讨论完以后,形成文档,然后依照文档进行开发!这样就有一个问题,你必需要保证你的接口是正确的. ...
- 使用IIS 7.0 Smooth Streaming 优化视频服务
http://www.cnblogs.com/dudu/archive/2013/06/08/iis_webserver_settings.html (支持高并发的IIS Web服务器常用设置) ht ...
- 具体解释Android中AsyncTask的使用
在Android中实现异步任务机制有两种方式,Handler和AsyncTask. Handler模式须要为每个任务创建一个新的线程,任务完毕后通过Handler实例向UI线程发送消息,完毕界面的更新 ...
- 关于Linux的10个核心面试问题与答案
转载:http://www.linuxeden.com/html/news/20140222/148676.html 又到了以轻松的心情来读些严肃内容的时刻了,哈!这是另一篇关于面试问题的文章,我们将 ...
- 用Linux安装光盘修复GRUB
转载:http://lgn21st.iteye.com/blog/179455 需要开视频会议,我不得零时从Ubuntu切换回去百年难道用一次的WinXP...发现自己的XP系统很混乱...决定重新装 ...
- Android WebView的使用方法总结
本文主要讲解WebView的一些常用使用方法 代码如下: xml文件: <LinearLayout xmlns:android="http://schemas.android.com/ ...
- 标签切换JS代码
//标签切换 var nav = $('.index-nav'); var content = $('.index-nav-content li'); function hoverNav ($eleA ...
- C中的回调函数
C语言中应用回调函数的地方非常多,如Nginx中: struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char *(*set)(ngx_c ...
- Url几个常用的函数
parse_url() 本函数解析一个 URL 并返回一个关联数组,包含在 URL 中出现的各种组成部分. 本函数不是用来验证给定 URL 的合法性的,只是将其分解为下面列出的部分.不完整的 URL ...
- HTML <base> 标签的 target 属性 —— <base target="_blank" />
为页面上所有链接规定默认目标: <head> <base target="_blank" /> </head> <body> < ...