【写给Cpp选手的C#教程】垃圾回收篇
作者:互联网
LINQ好长,先找个篇幅少的看一看。
一些东西需要显示释放资源,比如打开的文件。我们一般称显示释放为销毁。
由编译器自己释放内存的行为被称为垃圾回收,垃圾回收器一般用GC来简写?
有关销毁
需要销毁(显示释放资源)的类需要继承IDisposable接口。
public interface IDisposable{
void Dispose();
}
具体是这么使用的:
FileStream fs = new FileStream("file.txt",FileMode.Open)
{
try{/*something*/}
finally{if(fs!=null) ((IDisposable)fs),Dispose();}
}
然后有一个更加简洁的写法(感觉这玩意似曾相识啊)
using(FileStream fs = new FileStream("myFile.txt",FileMode.Open))
{/*something*/}
所以我们编写自定义的需要销毁(显示释放资源)的类时,需要实现IDisposable接口并且编写Dispose方法。
销毁的协议
有一个销毁的协议需要大家遵守:
①对象一旦销毁不能恢复,也不能重新激活。销毁后对其访问一般会抛出ObjectDisposedException。
②可以重复调用对象的Dispose方法,且不会发生错误。
③Dispose方法可以递归嵌套。若可销毁对象x“拥有”可销毁对象y,那么默认情况下x的Dispose方法会自动调用y的Dispose方法。
和Dispose()相似的函数
Close方法:关闭后可以重新打开。窗体使用Close()之后只会隐藏窗体,但Dispose()会释放窗体资源。
Stop方法:可能显示释放资源,但可以调用Start()方法重新开始。比如Timer。
什么时候销毁
不再使用时就销毁它。
也有不适合销毁的情况:
①你并不持有该对象时(我的理解是这个对象并不是你创建的,这只是个猜测,并不一定对)
②对象的Dispose方法产生了并不希望产生的副作用,或者说还没到销毁它的时候。(比如DbContext使用Close而不是Dispose)
③Dispose并不是必须的,并且用了Dispose会更麻烦
Dispose方法本身其实并不会释放内存,真正的内存释放要等到下次垃圾回收的时候。
因此可能会需要一个bool值来标注是否被销毁。
public bool IsDisposed{get;private set;}
如果有一些非常非常需要保密的数据,可以在编写Disposed的时候手动清理。
有关垃圾回收
有底子的应该都知道垃圾回收是啥,在此就不说了。不过C++没有垃圾回收,所以说几个细节。
①垃圾回收不是马上进行的,是有周期的。周期根据一些因素决定,可能几纳秒也有可能几天。
②因素比如:可用的内存数量、已经分配的内存数目、距离上次回收的时间间隔。
②垃圾回收并不会清理所有的垃圾。最近分配的会被更频繁回收。
具体实现
如果对象没有被根引用,那么就可以被回收了。
根是什么?根是以下三种之一:
①正在执行的方法(或在其调用栈中的任何一个方法)的局部变量或参数
②静态变量
③终结队列中的对象(见终结器)
相互循环引用的对象组在没有根引用的情况下是可以回收的
GC会从根对象开始按对象引用遍历对象图一直到遍历结束。结束后没有标记的对象会被当作垃圾进行回收。
WindowsRT依赖COM的引用计数机制,而不是自动垃圾回收。但C#实例化的WinRT对象的生命周期也是靠CLR的自动垃圾回收管理的。(看不懂啦)
有关终结器
看上去像是C++的析构函数。
class Test{
~Test(){}
}
终结器无法声明为public或static,不能有参数,不能调用基类。
有了对终结器的简单认知之后,就可以说明垃圾回收的流程了。
首先,垃圾回收器会确定可以删除的对象。对于没有终结器的对象将会被直接删除。有终结器的对象会保持存活,放入到一个特殊的队列中。
然后垃圾回收就完成了。应用程序接着执行。然后将会启动一个终结器线程,在此后的应用程序的执行过程中,终结器线程会取出队列中的对象,并且执行其终结方法。
等到被取出的对象的终结器方法执行完毕了,该对象就变成需要被回收的未引用对象了,将会在下一次垃圾回收时删除。
终结器和律师有相似之处,虽然它的存在非常必要,但除非绝对必要,通常不希望使用它。如果使用它,则要100%理解它所做的一切
如果终结器将即将销毁的对象引用到一个存活的对象上,那么当下一次垃圾回收发生时,CLR发现对象不再需要被销毁,因而就不会回收该对象。
垃圾回收器的工作方式
标准CLR使用分代式标记-压缩GC对托管堆上的对象进行内存管理。他会激活对象引用图来进行垃圾回收。
当内存分配量超过特定的阈(yv)值,或者程序需要降低内存使用量时,垃圾回收器会在进行内存分配(通过new关键字)时触发一次垃圾回收。(也可以通过System.GC.Collect方法手动触发)
在垃圾回收之后,剩余的存活对象将转移到堆的起始位置(被称为压缩),一方面防止内存碎片化,一方面避免了内存片段的维护开销。
分代回收
GC将堆内存分为三代从而进行优化。
第0代:刚刚分配的对象,一次回收大概不到1ms
第1代:在第一轮回收中存活的对象
第2代:不是第0代也不是第1代的对象
第0代分配到的空间少(最大256MB,通常只有几百KB到几MB)当第0代填满时会触发第0代垃圾回收。
第1代空间限制类似,内存回收也比较快速。
第2代并不是那么频繁,但一次完整的内存回收会包括第2代内存。对有着大对象图的程序的完整回收可能需要100ms。
第0代和第1代的回收会阻塞执行线程,第2代会尝试允许线程运行。具体是怎么处理,比较深奥了。
大对象堆
当对象过大(一般是大于85000字节)就会有其他堆来存放他,这个被称为大对象堆。这个堆用来避免过量的第0代回收。
因为移动大片内存的开销很大,所以大对象堆不会被压缩(移动到能到达的最前面的位置)。因而会有内存碎片,也需要对空闲内存块进行管理。大对象堆直接当作第2代来处理。
标签:教程,销毁,C#,Dispose,回收,对象,垃圾,Cpp,内存 来源: https://www.cnblogs.com/SandSea/p/15412452.html