【写给Cpp选手的C#教程】垃圾回收篇

2021/10/15 20:14:32

本文主要是介绍【写给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代来处理。



这篇关于【写给Cpp选手的C#教程】垃圾回收篇的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程