深⼊理解的垃圾回收(GC)机制
⼀、什么是GC
GC如其名,就是垃圾收集,当然这⾥仅就内存⽽⾔。Garbage Collector(垃圾收集器,在不⾄于混淆的情况下也成为GC)以应⽤程序的root为基础,遍历应⽤程序在Heap上动态分配的所有对象[2],通过识别它们是否被引⽤来确定哪些对象是已经死亡的、哪些仍需要被使⽤。已经不再被应⽤程序的root或者别的对象所引⽤的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。这就是GC⼯作的原理。为了实现这个原理,GC有多种算法。⽐较常见的算法有Reference Counting,Mark Sweep,Copy Collection等等。⽬前主流的虚拟系统 CLR,Java VM和Rotor都是采⽤的Mark Sweep算法。(此段内容来⾃⽹络)
的GC机制有这样两个问题:
⾸先,GC并不是能释放所有的资源。它不能⾃动释放⾮托管资源。
第⼆,GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。
GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接⼝,IDisposable接⼝定义了Dispo⽅法,这个⽅法⽤来供程序员显式调⽤以释放⾮托管资源。使⽤using语句可以简化
资源管理。
⼆、托管资源和⾮托管资源
托管资源指的是可以⾃动进⾏回收的资源,主要是指托管堆上分配的内存资源。托管资源的回收⼯作是不需要⼈⼯⼲预的,有运⾏库在合适调⽤垃圾回收器进⾏回收。
⾮托管资源指的是不知道如何回收的资源,最常见的⼀类⾮托管资源是包装操作系统资源的对象,例如⽂件,窗⼝,⽹络连接,数据库连接,画刷,图标等。这类资源,垃圾回收器在清理的时候会调⽤Object.Finalize()⽅法。默认情况下,⽅法是空的,对于⾮托管对象,需要在此⽅法中编写回收⾮托管资源的代码,以便垃圾回收器正确回收资源。
在中,Object.Finalize()⽅法是⽆法重载的,编译器是根据类的析构函数来⾃动⽣成Object.Finalize()⽅法的,所以对于包含⾮托管资源的类,可以将释放⾮托管资源的代码放在析构函数。
三、关于GC优化的⼀个例⼦
正常情况下,我们是不需要去管GC这些东西的,然⽽GC并不是实时性的,所以我们的资源使⽤完后,GC什么时候回收也是不确定的,所以会带来⼀些诸如内存泄漏、内存不⾜的情况,⽐如我们处理
⼀个约500M的⼤⽂件,⽤完后GC不会⽴刻执⾏清理来释放内存,因为GC不知道我们是否还会使⽤,所以它就等待,先去处理其他的东西,过⼀段时间后,发现这些东西不再⽤了,才执⾏清理,释放内存。
下⾯,来介绍⼀下GC中⽤到的⼏个函数:
GC.SuppressFinalize(this); //请求公共语⾔运⾏时不要调⽤指定对象的终结器。
GC.GetTotalMemory(fal); //检索当前认为要分配的字节数。 ⼀个参数,指⽰此⽅法是否可以等待较短间隔再返回,以便系统回收垃圾和终结对象。
GC.Collect(); //强制对所有代进⾏即时垃圾回收。
GC运⾏机制
写代码前,我们先来说⼀下GC的运⾏机制。⼤家都知道GC是⼀个后台线程,他会周期性的查找对象,然后调⽤Finalize()⽅法去消耗他,我们继承IDispo接⼝,调⽤Dispo⽅法,销毁了对象,⽽GC并不知道。GC依然会调⽤Finalize()⽅法,⽽在 中Object.Finalize()⽅法是⽆法重载的,所以我们可以使⽤析构函数来阻⽌重复的释放。我们调⽤完Dispo⽅法后,还有调⽤GC.SuppressFinalize(this) ⽅法来告诉GC,不需要在调⽤这些对象的Finalize()⽅法了。
下⾯,我们新建⼀个控制台程序,加⼀个Factory类,让他继承⾃IDispo接⼝,代码如下:
namespace GarbageCollect
{
public class Factory : IDisposable
{
private StringBuilder sb = new StringBuilder();
List list = new List();
//拼接字符串,创造⼀些内存垃圾
public void MakeSomeGarbage()
{
for (int i = 0; i < 50000; i++)
{
sb.Append(i.ToString());
}
南斯拉夫铁托
}
//销毁类时,会调⽤析构函数
~Factory()
{
Dispo(fal);
}
public void Dispo()
{
Dispo(true);
}
protected virtual void Dispo(bool disposing)
{
if (!disposing)海尔张瑞敏
{
return;
}
sb = null;
GC.Collect();
GC.SuppressFinalize(this);
}
}
}
只有继承⾃IDispo接⼝,使⽤这个类时才能使⽤Using语句,在main⽅法中写如下代码:
using System.Diagnostics;
namespace GarbageCollect
{
class Program
{
static void Main(string[] args)
{
using(Factory f = new Factory())
{
f.MakeSomeGarbage();
Console.WriteLine("Total memory is {0} KBs.", GC.GetTotalMemory(fal) / 1024);
}小学数学思想
Console.WriteLine("After GC total memory is {0} KBs.", GC.GetTotalMemory(fal) / 1024);
Console.Read();
}
}
}
运⾏结果如下,可以看到资源运⾏MakeSomeGarbage()函数后的内存占⽤为1796KB,释放后成了83Kb.
代码运⾏机制:
我们写了Dispo⽅法,还写了析构函数,那么他们分别什么时候被调⽤呢?我们分别在两个⽅法上⾯下断点。调试运⾏,你会发现先⾛到了Dispo⽅法上⾯,知道程序运⾏完也没⾛析构函数,那是因为我们调⽤了GC.SuppressFinalize(this)⽅法,如果去掉这个⽅法后,你会发现先⾛Dispo⽅法,后⾯⼜⾛析构函数。所以,我们可以得知,如果我们调⽤Dispo⽅法,GC就会调⽤析构函数去销毁对象,从⽽释放资源。
四、什么时候该调⽤GC.Collect
这⾥为了让⼤家看到效果,我显⽰调⽤的GC.Collect()⽅法,让GC⽴刻释放内存,但是频繁的调⽤GC.Collect()⽅法会降低程序的性能,除⾮我们程序中某些操作占⽤了⼤量内存需要马上释放,才可以显⽰调⽤。下⾯是官⽅⽂档中的说明:
垃圾回收 GC 类提供 GC.Collect ⽅法,您可以使⽤该⽅法让应⽤程序在⼀定程度上直接控制垃圾回收器。通常情况下,您应该避免调⽤任何回收⽅法,让垃圾回收器独⽴运⾏。在⼤多数情况下,垃圾回收器在确定执⾏回收的最佳时机⽅⾯更有优势。但是,在某些不常发⽣的情况下,强制回收可以提⾼应⽤程序的性能。当应⽤程序代码中某个确定的点上使⽤的内存量⼤量减少时,在这种情况下使⽤ GC.Collect ⽅法可能⽐较合适。例如,应⽤程序可能使⽤引⽤⼤量⾮托管资源的⽂档。当您的应⽤程
序关闭该⽂档时,您完全知道已经不再需要⽂档曾使⽤的资源了。出于性能的原因,⼀次全部释放这些资源很有意义。有关更多信息,请参见 GC.Collect ⽅法。
在垃圾回收器执⾏回收之前,它会挂起当前正在执⾏的所有线程。如果不必要地多次调⽤ GC.Collect,这可能会造成性能问题。您还应该注意不要将调⽤GC.Collect 的代码放置在程序中⽤户可以经常调⽤的点上。这可能会削弱垃圾回收器中优化引擎的作⽤,⽽垃圾回收器可以确定运⾏垃圾回收的最佳时间。
理解C#垃圾回收机制我们⾸先说⼀下CLR(公共语⾔运⾏时,Common Language Runtime)它和Java虚拟机⼀样是⼀个运⾏时环境,核⼼功能包括:内存管理、程序集加载、安全性、异步处理和线程同步。
乙型肝炎传播途径CTS(Common Type System)通⽤类型系统,它把.Net中的类型分为2⼤类,引⽤类型与值类型。.Net中所有类型都间接或直接派⽣⾄System.Object类型。所有的值类型都是System.ValueType的⼦类,⽽System.ValueType本⾝却是引⽤类型。
由CLR管理的存在于托管堆上的称为托管资源,注意这⾥有2个关键点,第⼀是由CLR管理,第⼆存在于托管堆上。托管资源的回收⼯作是不需要⼈⼯⼲预的,CLR会在合适的时候调⽤GC(垃圾回收器)进⾏回收。
青菜英语
⾮托管资源:
自查自纠个人报告
⾮托管资源是不由CLR管理,例如:Image Socket, StreamWriter, Timer, Tooltip, ⽂件句柄, GDI资源, 数据库连接等等资源(这⾥仅仅列举出⼏个常⽤的)。这些资源GC是不会⾃动回收的,需要⼿动释放。
通过上⾯的讲述总结⼀下,第⼀,GC(垃圾回收器)只回收托管资源,不回收⾮托管资源。第⼆,GC回收是要在合适的时候(CLR觉得应该进⾏回收的时候)才进⾏回收。那么⾮托管如何进⾏回收呢?下⾯就让我⼀⼀道来。神什么活什么
在.Net中释放⾮托管资源主要有2种⽅式,Dispo,Finalize
Dispo⽅法,对象要继承IDisposable接⼝,也就会⾃动调⽤Dispo⽅法。
代码如下:
Class Suifeng:System.IDisposable
{
#region IDisposable 成员
public void Dispo()
{
//
}
#endregion
}
Suifeng suiFeng= new Suifeng ();
suiFeng.Dispo();
//也可以使⽤Using语句
(using Suifeng suiFeng= new Suifeng())
{
/
/
}
Finalize()⽅法
MSDN上的定义是允许对象在“垃圾回收”回收之前尝试释放资源并执⾏其他清理操作。
它的本质就是析构函数
代码如下:
{
男女生网~Car() // destructor
{
//
}
}
该析构函数隐式地对对象的基类调⽤ Finalize。 这样,前⾯的析构函数代码被隐式地转换为以下代码:
代码如下:
protected override void Finalize()
{
try
{
//
}
finally
{
ba.Finalize();
}
}
在中应该尽可能的少⽤析构函数释放资源,MSDN2上有这样⼀段话:
实现 Finalize ⽅法或析构函数对性能可能会有负⾯影响,因此应避免不必要地使⽤它们。⽤ Finalize ⽅法回收对象使⽤的内存需要⾄少两次垃圾回收。当垃圾回收器执⾏回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些对象的项从终⽌队列中移除并将它们放置在标为准备终⽌的对象列表中。该列表中的项指向托管堆中准备被调⽤其终⽌代码的对象。垃圾回收器为此列表中的对象调⽤ Finalize ⽅法,然后,将这些项从列表中移除。后来的垃圾回收将确定终⽌的对象确实是垃圾,因为标为准备终⽌对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。
所以有析构函数的对象,需要两次,第⼀次调⽤析构函数,第⼆次删除对象。⽽且在析构函数中包含⼤量的释放资源代码,会降低垃圾回收器的⼯作效率,影响性能。所以对于包含⾮托管资源的对象,最好及时的调⽤Dispo()⽅法来回收资源,⽽不是依赖垃圾回收器。
在⼀个包含⾮托管资源的类中,关于资源释放的标准做法是:
继承IDisposable接⼝;
实现Dispo()⽅法,在其中释放托管资源和⾮托管资源,并将对象本⾝从垃圾回收器中移除(垃圾回收器不在回收此资源);
实现类析构函数,在其中释放⾮托管资源。
请看MSDN上的源码
代码如下: