GC机制

void test()

{

myClass myclass=new myClass();

}

  这里的myclass变量在托管栈上,new的对象在托管堆上,myclass存储了一个指向new对象的引用。CLR在运行此方法时,将托管栈指针移动,为局部变量myclass分配空间,当执行new时,CLR先查看托管堆是否有足够空间,足够的话就只是简单地移动下托管堆的指针来为myclass对象分配空间,如果托管堆没有足够空间,会引起垃圾收集器工作。CLR完成test方法执行时,托管栈上的myclass局部变量被立即删除,但是myclass对象的删除取决于垃圾收集器的触发条件。

垃圾收集器如何寻找不再使用的托管对象,并释放其占用的内存。

  垃圾收集器寻找不再使用的托管对象时,根据是否有引用指向它,没有就可以释放了。如果一个对象指向第二个对象,第二个指向第三个等,垃圾收集器会根据根来查找。  

  垃圾收集器先假定所有在托管堆里的对象都是不可到达的(没有被引用),然后从根上的那些变量开始,针对每个根上的变量,找出其引用的托管堆上的对象,将找到的对象加入图,然后沿着这个对象往下找,看有没有引用另一个对象,有的话,继续将找到的对象加入图,如果没有则说明这条链已经到了尾部。垃圾收集器就从根上的另一个变量开始找,直到所有的变量都找过了才停止查找。如果找过的对象已经加入图了,那么久不在此条链上继续查找,转去其他链上继续找。垃圾收集器建好这个图后剩下那些没有在这个图中的对象就可以被回收了。

  创建对象引用图后释放完不需要的对象,出现的内存碎片,垃圾回收器扫描托管堆,找到连续的内存块,然后移动未回收的对象到更低的地址以得到整块的内存,同时所有的对象引用都将被调整为指向对象新的存储位置。

  C#中的回收器分为三代,最近被分配的对象被置于第0代,经过一轮回收后仍然保留在第0代中的对象则被移入第1代中,再经过一轮回收仍然保留在第1代中的对象则被移入第2代。当第0代中没有可以分配的有效内存时就触发第0代垃圾回收,它将删除不再被引用的对象,并将当前正在使用的对象移入第1代。当第0代回收后仍然不能请求到充足的内存时就启动第1代垃圾回收。如果各代都进行了垃圾回收后仍然没有可用的内存就会引发一个OutOfMemoryException异常。

越新的对象,其生命周期越短。

越老的对象,其生命周期越长。

新对象之间通常有强的关系并被同时访问。

当程序开始运行时,垃圾收集器为每一代分配了一定的内存,这些内存的初始大小由framework的策略决定。垃圾收集器记录了这三代的内存地址和大小,这三代内存是连在一起的,第2代在第1代之下,第1代在第0代之下。如果第0代内存足够,CLR就简单快速地移动下指针,完成内存分配。当第0代内存不足以容纳新的对象时就触发第0代回收不需要的对象,回收完毕,垃圾收集器就夯实第0代中没有回收的对象到低地址,同时移动指针至空闲空间的开始地址(同时按照移动后的地址更新相关引用)。当只对第0代进行收集时,所发生的是部分收集。当垃圾收集器找到一个指向第1代或者第2代地址的根,垃圾收集器就忽略此根找其他根,如果找到指向第0代对象的根就将此对象加入图。这样就可以只处理第0代内存中的垃圾。这样有个先决条件,就是应用程序此前没有去写第1代和第2代的内存。但实际中是有可能写第1代或第2代的内存的。针对这种情况,CLR有专门的数据结构来标志应用程序是否曾经写第1代或第2代内存。如果此次对第0代进行收集之前应用程序写过第1代或第2代,那些被标志的对象将也要在此次对第0代收集时作为根。这样才可以正确地对第0代进行收集。每一代都维护着一个阀值。达到了就会触发收集。

GC.Collect()收集第0代,GC.Collect(1)收集第1代,GC.Collect(2)收集第2代。

对象结束

垃圾回收器使用”终止队列“的内部结构跟踪具有Finalize方法的对象。每次应用程序创建具有Finalize方法的对象时,垃圾回收器都在终止队列中放置一个指向该对象的项。当垃圾回收器执行回收时只回收没有终结器的不可访问对象的内存。具有终结器的不可访问对象的内存,垃圾收集器将其引用从结束队列移到待结束队列中,同时此对象会被加入引用关系图。一个独立运行的CLR线程将一个个从待结束队列取出对象,执行其Finalize方法以清理资源。因此对象不会马上被回收,只有此对象的Finalize方法被执行完毕后,其引用才会从待结束队列中移除。等下一轮回收时才会将其回收。GC.ReRegisterForFinalize是将指向对象的引用添加到结束队列中,GC.SuppressFinalize将结束队列中该对象的引用移除,CLR将不再回执行其Finalize方法。

对象结束机制对垃圾收集器的性能影响比较大,同时CLR难以保证调用Finalize方法的时间和次序。因此尽量不要用对象结束机制,而采用自定义的方法或名为Close,Dispose的方法来清理。可以考虑实现IDisposable接口并为Dispose方法写好清理资源的方法。

 大对象堆

  专用于存放大于85000字节的对象,初始的内存区域堆通常在第0代内存之上,并且与第0代内存不邻接。大对象堆尺寸比较大,收集时成本较高,所以对大对象堆的收集是在第2代收集时。大对象堆的收集也是从根开始查找可到达对象,不可到达的大对象就回收。垃圾收集器回收了大对象后,不会对大对象堆进行夯实操作,而是用一个空闲对象表的数据结构来登记哪些对象的空间可以再利用,其中两个相邻的大对象回收将在空闲对象表中作为一个对象对待。空闲对象表登记的空间将可以再分配新的大对象。

弱引用

  弱引用就是根有一个WeakReference类型的指针指向对象。垃圾收集器看到一个WeakReference类型的根指向某个对象就会特别处理。垃圾收集器创建对象引用关系图时遇到弱引用指针就不会加入图中。如果一个对象只有弱引用指向它,那么垃圾收集器可以收集此对象。一旦将一个强引用加到对象上,不管对象有没有弱引用,对象都不可回收。

垃圾收集器会弱引用的处理

WeakReferenct类的构造函数

WeakReferenct(Object target);

WeakReferenct(Object target,Boolean trackResurrection);

  垃圾收集器内部有一个短弱引用表,用WeakReferenct(Object)声明的弱引用对象将不会在托管堆中分配空间,而是在短弱引用表中分配一个槽。此槽中记录对对象的引用。

  如果用WeakReferenct(Object target,Boolean trackResurrection)声明一个弱引用对象,第二个参数为true的话,表示我们要跟踪该对象的重生。垃圾收集器内部有一个长弱引用表,这样声明的弱引用对象会在长弱引用表中分配一个槽,此槽中记录对对象的引用。

垃圾收集器此时的收集流程是这样的:

1、垃圾收集器建立对象引用图,来找到所有的可到达对象。遇到非WeakReference指针就加入图,遇到WeakReference指针则不加入。这样图就建好了。

2、垃圾收集器扫描弱引用表。如果一个指针指向一个不在图中的对象,那么此对象就是不可到达的,垃圾收集器就将短弱引用表相应的槽置空。

3、垃圾收集器扫描结束队列。如果队列中一个指针指向一个不在图中的对象,此指针将被从结束队列移到待结束队列,同时此对象被加入引用关系图,因为此对象是Finalize可到达的。

4、垃圾收集器扫描长弱引用表。如果一个指针指向一个不在图中的对象(此时图中已包含Finalize可到达的对象),那么此对象就是一个不可到达的对象,垃圾收集器就将长弱引用表相应的槽置空。

5、垃圾收集器夯实托管堆。

  短弱引用不跟踪重生。即垃圾收集器发现一个对象为不可到达就立即将短弱引用表相应的槽置空。如果该对象有Finalize方法并且没执行。如果应用程序访问弱引用对象的Target属性,即使该对象还存在,也会得到null。

  长弱引用跟踪重生。即垃圾收集器发现一个对象是Finalize可到达的对象,就不将相应的槽置空。因为Finalize方法还没执行,该对象就还存在。如果应用程序访问弱引用对象的Target属性可以得到对象;但如果Finalize方法已被执行就表明该对象没有重生。

  弱引用是为大对象准备的。在实际中,如果不用弱引用,只用强引用,则用过了该大对象然后将强引用置null,让GC可以回收它,但是没过多久又要用这个大对象,又得重新创建,比较浪费资源;而如果不置null,就会占用很多内存资源。对于这种情况,我们可以创建一个这个大对象的弱引用,这样内存不够时将强引用置null,让GC可以回收,而在没有被GC回收前,如果我们短时间内还需要该大对象,我们还可以再次找回该对象。

垃圾收集的一般流程:

1、挂起所有线程。

2、找到可回收的对象。

3、回收可回收的对象并压缩托管栈。

4、继续所有线程。

原文地址:https://www.cnblogs.com/buzhidaojiaoshenme/p/6850199.html