关于.net的一些基础知识(一)
一、GC工作原理:
GC如其名,就是垃圾收集,当然这里仅就内存而言。Garbage Collector(垃圾收集器,在不至于混淆的情况下也成为GC)以应用程序的root为基础,遍历应用程序在Heap上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是已经死亡的哪些仍需要被使用。已经不再被应用程序的root或者别的对象所引用的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。这就是GC工作的原理。为了实现这个原理,GC有多种算法。比较常见的算法有Reference Counting,Mark Sweep,Copy Collection等等。目前主流的虚拟系统.net CLR,Java VM和Rotor都是采用的Mark Sweep算法。
Mark Sweep:
在程序运行的过程中,不断的把Heap的分配空间给对象,当Heap的空间被占用到不足 以为下一个对象分配的时候Mark Sweep算法被激活,将垃圾内存进行回收并将其返回到free list中。
Mark Sweep就像它的名字一样在运行的过程中分为两个阶段,Mark阶段和Sweep阶段。Mark阶段的任务是从root出发,利用相互的引用关系遍历整个Heap,将被root和其它对象所引用的对象标记起来。没有被标记的对象就是垃圾。之后是Sweep阶段,这个阶段的任务就是回收所有的垃圾。
Mark Sweep算法虽然速度比Reference Counting要快,并且可以避免循环引用造成的内存泄漏。但是也有不少缺点,它需要遍历Heap中所有的对象(存活的对象在Mark阶段遍历,死亡的对象在Sweep阶段遍历)所以速度也不是十分理想。而且对垃圾进行回收以后会造成大量的内存碎片。
Generation:
Generational garbage collector(又被称为ephemeral garbage collector)是基于以下几个假设的:
l 对象越年轻则它的生命周期越短;
l 对象越老则它的生命周期越长;
l 年轻的对象和其它对象的关系比较强,被访问的频率也比较高;
l 对Heap一部分的回收压缩比对整个Heap的回收压缩要快。
Generation的概念就是对Heap中的对象进行分代(分成几块,每一块中的对象生存期不同)管理。当对象刚被分配时位于Generation 0中,当Generation 0的空间将被耗尽时,Mark Compact算法被启动。经过几次GC后如果这个对象仍然存活则会将其移动到Generation 1中。同理,如果经过几次GC后这对象还是存活的,则会被移动到Generation 2中,直到被移动到最高级中最后被回收或者是同程序一同死亡。 采用Generation的最大好处就在于每次GC不用对整个Heap都进行处理,而是每次处理一小块。对于Generation 0中的对象,因为它们死亡的可能性最大,所以对它们GC的次数可以安排多一些,而其它相对死亡的可能性小一些的对象所在的Generation可以少安排几次GC。这样做就使得GC的速度得到了一定程度的提高。这样就产生了几个有待讨论的问题,首先是应该设置几个Generation,每个Generation应该设置成多大,然后是对每个对象升级时它应该是已被GC了多少次而仍然存活。关于.net CLR对这个问题的处理,在本文的最后将给出一个例子对其进行测试。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; namespace gcTest { class gcDemo { private static void GenerationDemo() { // Let's see how many generations the GCH supports (we know it's 2) Console.WriteLine("Maximum GC generations: {0}", GC.MaxGeneration); // Create a new BaseObj in the heap GenObj obj = new GenObj("Generation"); // Since this object is newly created, it should be in generation 0 obj.DisplayGeneration(); // Displays 0 for(int i = 1; i <= GC.MaxGeneration; i++) { // Performing a garbage collection promotes the object's generation GC.Collect(); obj.DisplayGeneration(); // Displays i } obj = null; // Destroy the strong reference to this object for(int i = 0; i <= GC.MaxGeneration; i++) { GC.Collect(i); GC.WaitForPendingFinalizers(); //suspend this thread until the freachable queue of //the i generation has been emptied //only when i = GC.MaxGeneration, this finalization method //of obj will be performed } Console.WriteLine("Demo stop: Understanding Generations."); //total gc times //generation 0 : 5 times //generation 1 : 4 times //generation 2 : 3 times } public static void Main() { GenerationDemo(); } } class GenObj { private string objName; public GenObj(string name) { this.objName = name; } public void DisplayGeneration() { Console.WriteLine("I am in Generation {0}", GC.GetGeneration(this)); } }; }
这是个有趣的例子,首先利用GC.MaxGeneration()得知了在.net CLR中的GC采用了3代的结构,即Generation 0~2。接下来在Managed Heap上分配了一个GenObj的实例obj。在开始时obj位于Generation 0中,然后对整个Managed Heap进行两次GC。可以发现每进行一次GC存活的对象都会升一级直至到达Generation 2中。设置obj = null,这样做是为了取消root对obj的强引用,使obj成为垃圾。紧接着利用GC.Collect(i)对Managed Heap逐级进行GC,这个方法会对Generation 0~i进行GC。GC.WaitForPendingFinalizers()的作用是使整个进程挂起,等到Freachable Queue中所指向的对象的Finalize方法被调用。这样做的目的是为了保障对本次GC所确定的垃圾进行完全的回收,而不会因为对象的Finalize方法使对象复生。
二、using与new区别:
using两种用法;1.调用命名空间 2.声明一个生存区间,在区间之外,区间内声明的变量自动消亡,被回收,当对象使用了非托管资源或是比较宝贵的资源时使用;
new有两种用法:1.实例化一个对象 2.声明隐藏基类方法
三、托管资源与非托管资源:
最常见的一类非托管资源就是包装操作系统资源的对象,例如文件,窗口或网络连接,对于这类资源虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。还好.net Framework提供了Finalize()方法,它允许在垃圾回收器回收该类资源时,适当的清理非托管资源。如果在MSDN Library 中搜索Finalize将会发现很多类似的主题,这里列举几种常见的非托管资源:ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader ,Pen,Regex,Socket,StreamWriter,Timer,Tooltip 等等资源。可能在使用的时候很多都没有注意到!
关于托管资源,就不用说了撒,像简单的int,string,float,DateTime等等,.net中超过80%的资源都是托管资源。
非托管资源如何释放,.NET Framework 提供 Object.Finalize 方法,它允许对象在垃圾回收器回收该对象使用的内存时适当清理其非托管资源。默认情况下,Finalize 方法不执行任何操作。默认情况下,Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您必须在类中重写 Finalize 方法。然而大家都可以发现在实际的编程中根本无法override方法Finalize(),在C#中,可以通过析构函数自动生成 Finalize 方法和对基类的 Finalize 方法的调用。