C# GC

以前看了关于GC部分的文章,但是由于当时并没有真正理解,所以导致记忆也非常短暂,几年过去忘得一干二净,最近又看了些文章,综合了好几篇文章,这里总结一下。

首先贴上参考文章,这里我写下来只为了自己的理解和记录。

https://www.iteye.com/blog/touchmm-1108597

https://www.cnblogs.com/nele/p/5673215.html

许多核心原理在第二篇文章中都有所讲解,GC的定义、原理,包括目前CLR使用的Mark Sweep算法。这里只想总结一下重点想要记忆的东西。

1.Mark-Compact 标记压缩算法

简单地把.NET的GC算法看作Mark-Compact算法。假设所有对象可回收并且打上标记,之后找出不可回收的对象一一清除标记然后回收对象,回收之后heap中原本分配的连续空间由于某些对象被回收导致内存空间不连续,接下来会将他们移动,在heap中重新分配成一个基于heap基地址的连续空间,并且进行指针修复。

                            

2.Finalization Queue和Freachable Queue

这两个队列和.NET对象所提供的Finalize方法有关。这两个队列并不用于存储真正的对象,而是存储一组指向对象的指针。当程序中使用了new操作符在Managed Heap上分配空间时,GC会对其进行分析,如果该对象含有Finalize方法则在Finalization Queue中添加一个指向该对象的指针。

这里让我产生了疑问,为什么说了要有finalize方法而后面去利用GC机制来释放非托管资源的时候却没有声明finalize方法呢。后来才知道,finalize方法其实就是析构函数。编译器在编译析构函数的时候,就会声明成一个finalize方法。

在GC被启动以后,经过Mark阶段分辨出哪些是垃圾。再在垃圾中搜索,如果发现垃圾中有被Finalization Queue中的指针所指向的对象,则将这个对象从垃圾中分离出来,并将指向它的指针移动到Freachable Queue中。这个过程被称为是对象的复生(Resurrection),本来死去的对象就这样被救活了。为什么要救活它呢?因为这个对象的Finalize方法还没有被执行,所以不能让它死去。Freachable Queue平时不做什么事,但是一旦里面被添加了指针之后,它就会去触发所指对象的Finalize方法执行,之后将这个指针从队列中剔除,这是对象就可以安静的死去了。(所以,拥有finalize方法的对象,需要进行二次回收才能够真正将这个对象释放掉)

3.托管资源

.NET中的所有类型都是(直接或间接)从System.Object类型派生的。

  CTS中的类型被分成两大类——引用类型(reference type,又叫托管类型[managed type]),分配在内存堆上;值类型(value type),分配在堆栈上。如图:

                                      

值类型:

值类型的释放是由CLR自动管理的,当变量超出其作用域,就会自动释放掉。值类型的地址分配是从高(底部)往低(顶部)的

引用类型:

引用类型分配在托管堆(Managed Heap)上,声明一个变量在栈上保存,当使用new创建对象时,会把对象的地址存储在这个变量里。托管堆相反,从低地址往高地址分配内存。

                               

4.非托管资源

ApplicationContext, Brush, Component, ComponentDesigner, Container, Context, Cursor, FileStream, Font, Icon, Image, Matrix, Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI资源, 数据库连接等等资源。可能在使用的时候很多都没有注意到!

  .NET的GC机制有这样两个问题:

  首先,GC并不是能释放所有的资源。它不能自动释放非托管资源。

  第二,GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。

       由于GC这样的缺陷,.Net也提供了供程序员们手动管理资源释放的框架,就是IDisposable。简单来说,finalize为自动,dispose为手动。

经典案例:

public class ResourceHolder : System.IDisposable

{

    public void Dispose()

    {

        Dispose(true);

        System.GC.SuppressFinalize(this);

        // 上面一行代码作用是防止"垃圾回收器"调用这个类中的析构方法

        // " ~ResourceHolder() "

        // 为什么要防止呢? 因为如果用户记得调用Dispose()方法,那么

        // "垃圾回收器"就没有必要"多此一举"地再去释放一遍"非托管资源"了

        // 如果用户不记得调用呢,就让"垃圾回收器"帮我们去"多此一举"吧 ^_^

        // 你看不懂我上面说的不要紧,下面我还有更详细的解释呢!

    }

    protected virtual void Dispose(bool disposing)

    {

        if (disposing)
    
        {

        // 这里是清理"托管资源"的用户代码段。

        }

        // 这里是清理"非托管资源"的用户代码段。此处为析构方法的实际执行代码,为了避免客户代码忘记显示调用Dispose()方法,所作的备份。

    }

    ~ResourceHolder()

    {

        Dispose(false); // 这里是清理"非托管资源"

    }

}

当我们调用Dispose()方法(也就是调用Dispose(true))时,就会同时清理托管资源和非托管资源并且这时候告诉GC不要再调用finalize方法(析构函数)了因为我已经手动清理过了不需要你了。但是程序写代码难免犯错,假如忘记手动释放了,那么GC自动帮我们调用finalize方法帮忙清理非托管资源(然后在二次清理时清理掉托管资源)就成了一层保障。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值