From: http://www.c-sharpcorner.com/UploadFile/shivprasadk/657567608232009132704PM/6575676.aspx
注: 非一字不漏的翻译,主要是记录过程中的学习,方便加深理解。
在析构函数中进行非托管资源的清理工作会对性能和内存消耗造成较大的影响,在析构函数中编写清理代码会导致GC双倍的访问,自然导致性能的影响。
首先需要了解一下generation的概念和finalize dispose模式。
本文采用CLR Profiler。需要了解的话可以看这里:http://www.c-sharpcorner.com/UploadFile/shivprasadk/452069230108152009163244PM/4520692301.aspx?ArticleID=50bdd822-23d0-4baa-ab0a-21314b94d9e5
在前言中提到不恰当的析构代码会导致GC双倍的访问,许多程序员问:”我们真的需要关心GC,了解GC是如何做的么?”。是的,
事实上我们不需要关心Gc是如何做的,只要我们写出来恰当的合适的(property)代码。GC采用最好的算法来保证程序不受到影响,但是我们写代码和清理分配内存资源的方式会影响GC的算法,有些情况下这会导致GC的低效,从而导致应用程序的低效。
先来看看GC在分配和清理内存的时候会做什么吧。
假设我们有3个类。 A,B, C,相互引用关系如下图所示:
当应用程序启动的时候,应用程序会得到一块预先分配的内存,当应用程序创建了3个对象的时候,这个对象被分配了内存中的相应的空间。答题如下图所示:(注:特点是顺序分配)
GC内部维护一个对象关系图,通过该图可以知道哪些对象是可以Reachable的。所有的对象都与应用程序的Root Object,RootObject也维护着对象的实际地址,换句话说,Roots Object持有所有的对象的引用。如下图所示:
假如现在对象A被从内存中释放了,那么对象A的内存被分配给了对象B,对象B的内存被分配给了对象C(注:这句话准确性存在问题,这句话的前提是对象A,B,C所占有的空间都是一样的,正确的描述应该是当对象A被释放的时候,GC会回收对象A的空间,并且将对象B和对象C移动到以对象A起始地址为开始的内存空间中,并且调整相关的内存引用关系)。大体如下图所示:
当对象的地址更新后,GC需要确保内部的对象图中持有的对象的相应的内存地址也得到更新。更新后的对象关系图如下所示。GC现在需要做的是从对象关系图中移除对象A,并且确保目前仍然存在的对象的地址都是正确的最新的。
应用程序除了自定义的对象外,还有.net 自身的对象,这些一起构成了对象关系图。当内存空间被重新整理后,.net对象的地址也需要更新。.net 运行时的对象数量是非常多,比如说下图是一个简单的控制台应用程序,对象的数量大概是1000, 更新这些对象的地址是巨大的任务。
GC使用generation的概念来提升性能。Generation的概念和人类处理任务的心理类似。下列是几点人类是如何处理任务的,GC也采用类似的方式。
- if you decide some task today there is a high possibility of completion of those tasks. 加入你决定在今天做某些事情,那么这些事情在今天被完成的可能性极高。
- if some task is pending from yesterday then probably that task has gained a low prority and it can be delayed further. 如果有昨天没有完成的任务,那么这些任务被赋予了较低的等级,可以被再次拖延。
- if some task is pending from day before yesterday then there is a huge probability that the task can be pending forever. 如果有前天遗留下来的任务,那么这些任务可以无限期的推迟。
GC采用类似的道理,采取了如下的假定:
- if the object is new then the life time of the object can be short .如果对象比较新,那么对象的生命周期比较短。
- if an object is old then it can have a long life time .如果对象比较老,那么对象的生命周期比较长。
Gc采取了3个代际。如下图所示:
Generation 0 上存放的所有的最新创建的对象,当应用程序创建对象的时候,首先将对象放到Generation 0上,一段时间过后,当Generation 0满了的时候,GC需要运行以便回收内存资源,GC开始构建对象关系图,识别哪些对象不会被应用程序再使用。当GC不能够将对象从Generation 0上释放掉的时候,它会把它提升到Generation 1,当GC仍然不能将对象从generation 1上移除的时候,GC会将其移动到Generation 2上。.net支持的最大的代际是2.
下图是通过CLR Profiler看到的不同的代际的对象。
由于对象是按照Generation存储的,GC可以决定清理那个代际的对象,根据对象的生命周期依据,GC会在Generation 0上运行比其他代际还要多的次数。假如没有从generation 0中清理出来足够多的内存,GC会清理generation 1,以此类推。
- huge number of object in gen1 and 2 means memory utilzation is not optimized. 大量的在Gen1和GEN2上使用的对象意味着内存的使用效率不高(注:这个有点类似餐馆的餐桌的使用效率。将餐馆比作是计算机系统,餐桌比较是内存空间,GC比作是服务员,那么只有进入餐馆吃饭的顾客吃饭效率非常高的情况下,餐馆的收益才算比较高)
- Large the gen1 and gen2 regions gc algorithm will perform more worst. gen1和gen2占用的区域越大,GC算法的效率越差劲。
析构函数会隐含的调用finalize方法,我们需要理解为什么事先析构函数会导致对象被移到gen1和gen2区域上,下面是详细的过程:
- when new objects are created they are moved to gen0. 对象是在第零代堆上被创建的。
- when gen 0 fills out gc runs and tries to clear memory. 第零代内存区域满了以后,GC会启动并且开始清理内存 。
- if the object do not have a desturctor then it just cleans them up if they are not used .假如对象没有析构函数并且不再被使用的话,GC会清理掉这些对象。
- if the objects has a finalize method it moves those objects to the finalization queue. 如果对象有finalize方法的话,GC会将这些对象移到一个finalization 队列中。
- if the objects are reachable it’s moved to the ‘Freachable’ queue. if the objects are unreachable the memory is reclaimed. 假如对象是可以到达的,对象被'Freachabel‘队列中了,如果对象是不可到达的,那么对象的内存区域会被回收。
- GC work is finished for this iteration. GC完成本次任务。
- Next time when gc again starts its goes to Freachable queue to check if the objects are not reachable. if the objects are not reachable frame freachable memory is claimed back .当GC再次启动的时候,GC会首先检查Freachable队列中的对象是否可达的日,如果对象是不可大的话,对象会被回收掉。
整体过程如下图所示:
换句话说,那些有析构函数的对象在内存中存活的时间要长一些。
看如下代码:
class clsMyClass
{
public clsMyClass()
{
}
~clsMyClass()
{
}
}
然后我们创建1000个对象采用CLR Profiler来观察内存的情况:
for (int i = 0; i < 100 * 10000; i++)
{
clsMyClass obj = new clsMyClass();
}
通过CLR Profiler 的memory by address视图可以看到很多对象在gen1上。
如果移除构造函数的话,可以看到对象多数都在gen0上,gen1和gen2上只有少数的对象。
两者对比如下所示:
通过实现IDispose接口可以避免使用析构函数,采用这种方式编写finalize方法,并且在finalize方法中调用suppress finalize方法。方法SuppressFinalize提示GC不要调用Finalize方法。所以双倍的GC调用不会出现。
class clsMyClass : IDisposable
{
public clsMyClass()
{
}
~clsMyClass()
{
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
//The client now needs to ensure that it calls the dispose method as shown below.
for (int i = 0; i < 100; i++)
{
clsMyClass obj = new clsMyClass();
obj.Dispose();
}
下图是采用了Dispose模式和析构函数模式的对比。可以看到在第零代的内存分配得到了很好的改善。
如果忘记调用Dispose方法怎么办,可以采用finalzie/Dispose模式来说处理。详细的实现模式参见:
http://msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx
代码如下:
class clsMyClass : IDisposable
{
public clsMyClass()
{
}
private void CleanUp()
{
// Your clean up code goes here
}
~clsMyClass()
{
// In case the client forgets to call
// Dispose , destructor will be invoked for
// clean up
CleanUp();
}
public void Dispose()
{
// Call the clean up
CleanUp();
// Ensure that the destructor is not called
GC.SuppressFinalize(this);
}
}
Conclusion
- Do not have default constructors in your classes.
- In case you need to clean up use finalize dispose pattern with 'SupressFinalize' method called.
- If there is a dispose method exposed by a class , ensure to call the same from your client code.
- Application should have more objects allocated in Gen 0 than Gen 1 and Gen 2. More objects in Gen 1 and 2 is sign of bad GC algorithm execution.
作者的FAQ..
I do understand that this is not the right article to talk about my FAQ's. Just wanted to pat myself to complete 1 year of writing for my FAQ series. Below is the consolidated links for all:-
Silverlight FAQ :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/21FAQ04242009031713AM/21FAQ.aspx
LINQ FAQ :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/654654607132009040318AM/6546546.aspx?
WWF FAQ :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/12334512312008070235AM/123345.aspx
WCF FAQ :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/122345601022009064602AM/1223456.aspx
Sharepoint FAQ :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/1234567801062009045241AM/12345678.aspx
Localization and globalization :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/1112401242009043742AM/11124.aspx
Project management FAQ :-
http://www.c-sharpcorner.com/UploadFile/shivprasadk/PMCosting04132009051929AM/PMCosting.aspx