.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回收机制。