GC的前世与今生(二)

简介: GC的前世与今生(二)

三、Finalization Queue和Freachable Queue


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


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


.NET Framework的System.GC类提供了控制Finalize的两个方法,ReRegisterForFinalize和SuppressFinalize。前者是请求系统完成对象的Finalize方法,后者是请求系统不要完成对象的Finalize方法。ReRegisterForFinalize方法其实就是将指向对象的指针重新添加到Finalization Queue中。这就出现了一个很有趣的现象,因为在Finalization Queue中的对象可以复生,如果在对象的Finalize方法中调用ReRegisterForFinalize方法,这样就形成了一个在堆上永远不会死去的对象,像凤凰涅槃一样每次死的时候都可以复生。


托管资源:


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


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


如图:


image.png

值类型在栈里,先进后出,值类型变量的生命有先后顺序,这个确保了值类型变量在退出作用域以前会释放资源。比引用类型更简单和高效。堆栈是从高地址往低地址分配内存。


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


如图:

image.png


.NET中超过80%的资源都是托管资源。


非托管资源:

  

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并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using语句可以简化资源管理。


示例:
///summary
/// 执行SQL语句,返回影响的记录数
////summary
///param name="SQLString"SQL语句/param
///returns影响的记录数/returns
publicstaticint ExecuteSql(string SQLString)
{
    using (SqlConnection connection =new SqlConnection(connectionString))
    {
        using (SqlCommand cmd =new SqlCommand(SQLString, connection))
        {
            try
            {
                connection.Open();
                int rows = cmd.ExecuteNonQuery();
                return rows;
            }
            catch (System.Data.SqlClient.SqlException e)
            {
                connection.Close();
                throw e;
            }
            finally
            {
                cmd.Dispose();
                connection.Close();
            }
        }
    }
}
  当你用Dispose方法释放未托管对象的时候,应该调用GC.SuppressFinalize。如果对象正在终结队列(finalization queue), GC.SuppressFinalize会阻止GC调用Finalize方法。因为Finalize方法的调用会牺牲部分性能。如果你的Dispose方法已经对委托管资源作了清理,就没必要让GC再调用对象的Finalize方法(MSDN)。附上MSDN的代码,大家可以参考。
publicclass BaseResource : IDisposable
{
    // 指向外部非托管资源
private IntPtr handle;
    // 此类使用的其它托管资源.
private Component Components;
    // 跟踪是否调用.Dispose方法,标识位,控制垃圾收集器的行为
    privatebool disposed =false;
    // 构造函数
public BaseResource()
    {
        // Insert appropriate constructor code here.
    }
    // 实现接口IDisposable.
    // 不能声明为虚方法virtual.
    // 子类不能重写这个方法.
    publicvoid Dispose()
    {
        Dispose(true);
        // 离开终结队列Finalization queue
        // 设置对象的阻止终结器代码
        //
        GC.SuppressFinalize(this);
    }
    // Dispose(bool disposing) 执行分两种不同的情况.
    // 如果disposing 等于 true, 方法已经被调用
    // 或者间接被用户代码调用. 托管和非托管的代码都能被释放
    // 如果disposing 等于false, 方法已经被终结器 finalizer 从内部调用过,
    //你就不能在引用其他对象,只有非托管资源可以被释放。
    protectedvirtualvoid Dispose(bool disposing)
    {
        // 检查Dispose 是否被调用过.
if (!this.disposed)
        {
            // 如果等于true, 释放所有托管和非托管资源
if (disposing)
            {
                // 释放托管资源.
                Components.Dispose();
            }
            // 释放非托管资源,如果disposing为 false,
            // 只会执行下面的代码.
            CloseHandle(handle);
            handle = IntPtr.Zero;
            // 注意这里是非线程安全的.
            // 在托管资源释放以后可以启动其它线程销毁对象,
            // 但是在disposed标记设置为true前
            // 如果线程安全是必须的,客户端必须实现。
        }
        disposed =true;
    }
    // 使用interop 调用方法
    // 清除非托管资源.
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    privateexternstatic Boolean CloseHandle(IntPtr handle);
    // 使用C# 析构函数来实现终结器代码
    // 这个只在Dispose方法没被调用的前提下,才能调用执行。
    // 如果你给基类终结的机会.
    // 不要给子类提供析构函数.
~BaseResource()
    {
        // 不要重复创建清理的代码.
        // 基于可靠性和可维护性考虑,调用Dispose(false) 是最佳的方式
        Dispose(false);
    }
    // 允许你多次调用Dispose方法,
    // 但是会抛出异常如果对象已经释放。
    // 不论你什么时间处理对象都会核查对象的是否释放,
    // check to see if it has been disposed.
publicvoid DoSomething()
    {
        if (this.disposed)
        {
            thrownew ObjectDisposedException();
        }
    }
    // 不要设置方法为virtual.
    // 继承类不允许重写这个方法
    publicvoid Close()
    {
        // 无参数调用Dispose参数.
        Dispose();
    }
    publicstaticvoid Main()
    {
        // Insert code here to create
        // and use a BaseResource object.
    }
}


目录
相关文章
|
存储 缓存 Java
释放C盘空间:释放Windows休眠文件和关闭虚拟内存
在 Windows 11 专业版中,可以通过以下步骤来释放休眠文件(Hibernate File),以释放磁盘空间。休眠文件是系统休眠(Hibernate)功能所需要的文件,它保存了系统的当前状态,以便在休眠状态下恢复。如果你不使用休眠功能,如果因为C盘空间不足,可以考虑释放这个文件来腾出磁盘空间。
26042 1
|
12月前
|
分布式计算 监控 JavaScript
验阿里云的云应用开发平台CAP
验阿里云的云应用开发平台CAP
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
605 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
存储 应用服务中间件 API
高效C++项目实战:秋招简历项目解析(提供源码下载)
高效C++项目实战:秋招简历项目解析(提供源码下载)
|
存储 安全 Unix
C#.Net筑基-类型系统②常见类型--日期和时间的故事
在System命名空间中,有几种表示日期时间的不可变结构体(Struct):DateTime、DateTimeOffset、TimeSpan、DateOnly和TimeOnly。DateTime包含当前本地或UTC时间,以及最小和最大值;DateTimeOffset增加了时区偏移信息,适合跨时区操作。UTC是世界标准时间,而格林尼治标准时间(GMT)不稳定,已被更精确的UTC取代。DateTimeOffset和DateTime提供了转换为UTC和本地时间的方法,以及各种解析和格式化函数。
222 5
|
SQL 关系型数据库 MySQL
大量delete mysql的数据时,为什么导致OOM
大量delete mysql的数据时,为什么导致OOM
|
小程序 前端开发 Java
基于微信小程序的鲜花预定系统的设计与实现
基于微信小程序的鲜花预定系统的设计与实现
539 0
|
Linux 编译器 C语言
Linux下编译安装最新版gcc
Linux下如何自行升级到最新版的gcc
|
弹性计算 Linux Anolis
阿里云服务器Anolis OS镜像龙蜥操作系统全解析
阿里云Anolis OS镜像龙蜥操作系统全解析
3891 0
阿里云服务器Anolis OS镜像龙蜥操作系统全解析
|
存储 弹性计算 运维
从备份升级到容灾,利用阿里云就可以做到的灾备方案
从备份升级到容灾,利用阿里云就可以做到的灾备方案
从备份升级到容灾,利用阿里云就可以做到的灾备方案