当前位置: 首页 > news >正文

CLR Via CSharp读书笔记(21):自动内存管理(垃圾回收)

#1 垃圾回收平台的基本工作原理

访问一个资源所需的具体步骤:

1)调用IL指令newobj,为代表资源的类型分配内存。在C#中使用new操作符,编译器就会自动生成该指令。
2)初始化内存,设置资源的初始状态,使资源可用。类型的实例构造器负责设置该初始状态。
3)访问类型的成员(可根据需要反复)来使用资源。
4)摧毁资源的状态以进行清理。正确清理资源的代码要放在Finalize, DisposeClose方法。
5)释放内存。垃圾回收器独自负责这一步。

托管堆如何知道应用程序不再用一个对象

托管堆是CLR中自动内存管理的基础。初始化新进程时,运行时会为进程保留一个连续的地址空间区域。这个保留的地址空间被称为托管堆。托管堆维护着一个指针(NextObjPtr),用它指向将在堆中分配的下一个对象的地址。最初,该指针设置为指向托管堆的基址。

newobj指令将导致CLR执行以下步骤:

1) 计算类型(及其所有基类型)的字段所需要的字节数

2) 加上对象的开销所需要的字节数。包括类型对象指针同步索引块。32位程序需要增加8字节,64位程序需要增加16字节。

3) CLR检查保留区域是否能够提供分配对象所需要的字节数,如有必要就提交存储。如果托管堆有足够的可用空间,对象会被放入。

托管堆上连续分配的对象会由于引用的locality而获得性能上的提升,而且对象可以全部驻留在CPU缓存中,不会因为cache miss而被迫访问较慢的RAM.

托管堆之所有有这些好处,是因为它做了一个假设--地址空间和存储是无限的。托管堆通过垃圾回收器来允许它做这样的假设。

应用程序调用new操作符创建对象时,如果第0代堆满,执行一次垃圾回收。在一次垃圾回收中存活下来的对象被提升到另一代(例如第1代)。

#2 垃圾回收算法

每个应用程序都包含一组根(root)。静态字段方法参数局部变量均被认为是一个根。只有引用类型的变量才被认为是根。值类型的变量永远都不被认为是根。此外,CPU寄存器也被视作根。

垃圾回收器开始执行时,假设堆中的所有对象都是垃圾,然后通过标记(对有跟引用的进行标记)和压缩(回收没有标记的对象)进行垃圾回收。

标记阶段:垃圾回收器沿着线程栈上行以检查所有根,如果发现一个跟(root)引用了一个对象(直接引用或间接引用),就在对象的"同步索引字段"上开启一位(将一个bit设置为1)进行标记。

压缩阶段:垃圾回收器线性的遍历堆,以寻找未标记对象的连续内存块。若果内存块较小,垃圾回收器会忽略该块。移动内存中的对象后,包含"指向这些对象的指针"的变量和CPU寄存器现在都会变得无效,垃圾回收器需要遍历修改所有根来指向新的内存位置。

#3 垃圾回收与调试

class DebuggingRoots {
    public static void Go() {
        var t = new System.Threading.Timer(TimerCallback, null, 0, 2000);
        Console.ReadLine();

        // 在ReadLine之后引用t,这种方式会被编译器优化掉
        //t = null;

        // 在ReadLine之后引用t,防止其在Dispose方法返回之前被垃圾回收
        //t.Dispose();
    }

    private static void TimerCallback(Object o) {
        Console.WriteLine("In TimerCallback: " + DateTime.Now);
        // 出于演示目的强制执行垃圾回收
        GC.Collect();
    }
}

#4 使用终结操作释放本地资源

System.Threading.Mutex类型要打开一个Windows互斥体内核对象(本地资源)并保存其句柄,并在调用Mutex的方法时使用该句柄。

值类型(含所有枚举类型)、集合类型、String、Attribute、Delegate和Exception所代表的资源无需执行特殊的清理操作。如果一个类型代表着(或包装着)一个非托管资源(比如文件、数据库连接、套接字、mutex、位图、图标等),在对象的内存准备回收时,必须执行一些清理代码。实现了Finalize方法的任何类型实际上是在说,它的所有对象都希望"在被处决之前吃上最后一餐"。

1) 使用CriticalFinalizerObject类型确保终结

CLR赋予这个类以下三个功能:

首次构造任何CriticalFinalizerObject派生类型的一个对象时,CLR立即对继承层次结构中的所有Finalize方法进行JIT编译以确保其肯定得到执行。如果不对Finalize方法进行提前编译,则在内存紧张时,CLR可能找不到足够的内存来编译Finalize方法,这会阻止方法的执行,造成本地资源泄露。此外,如果Finalize方法中的代码引用了另外一个程序集中的一个类型,而且CLR在寻找这个程序集时失败,也会造成资源得不到释放。

CLR是在调用了非CriticalFinalizerObject派生类型的Finalize方法之后,才调用CriticalFinalizerObject派生类型的Finalize方法。例如,FileStream类的Finalize方法可以放心的将数据从内存缓冲区flush到磁盘,它知道此时磁盘文件还没有关闭

如果AppDomain被一个宿主应用程序(SQL Server或Asp.NET)强行中断,CLR将调用CriticalFinalizerObject派生类型的Finalize方法。

2) SafeHandle类型及其派生类型

对于SafeHandle类,需要注意: 其一,它派生自CriticalFinalizerObject,这确保它会得到CLR的特殊对待; 其二,它是一个抽象类,必须有另外一个类从该类派生,并重写受保护的构造器、抽象方法ReleaseHandle以及抽象属性IsValidget访问器方法。

3)使用SafeHandle类型与非托管代码进行互操作

与非托管代码进行交互时,SafeHandle提供了另外两个功能: CLR调用Win32 CreateEvent函数,函数返回到托管代码时,新的SafeWaitHandle(CLR知道其从SafeHandle派生)对象的构造以及句柄的赋值是在非托管代码中发生的,不可能被一个ThreadAbortException打断。

SafeHandle派生类的最后一个功能是防止有人利用潜在的安全漏洞。当一个线程可能试图使用一个本地资源,同时另一个线程试图释放该资源,这可能造成一个句柄循环使用漏洞。SafeHandle类防范这个安全隐患的办法是使用引用计数

CriticalHandle类除了不提供引用计数器功能,其他方面与SafeHandle类相同。CriticalHandle类及其派生类通过牺牲安全性来换取更好的性能。

#5 对托管资源使用终结操作:

虽然终结操作几乎专供释放本地资源,但偶尔也用于托管资源。Finalize方法中调用的任何代码都不能使用其他任何可能已经终结的对象

即使类型的实例构造器抛出了异常,类型的Finalize方法也会被调用。因此,Finalize方法不应假设对象处于良好、一致的状态。

设计一个类型时,处于性能方面的原因,最好避免使用Finalize方法,因为必须进行额外的处理(分配的时候要将指针放到终结列表,对象可能会提升到较老的代回收时须进行额外处理).

Finalize方法在垃圾回收发生时运行,而垃圾回收可能在应用程序请求更多内存时才发生;CLR不保证各个Finalize方法的调用顺序;调用静态方法需要注意方法是否在内部访问已终结了的对象。完全可以放心的访问值类型的实例,或者没有定义Finalize方法的引用类型的实例

#6 导致Finalize方法被调用的5种事件

第0代满(最常见的方式)
代码显示调用System.GC的静态方法Collect
Windows报告内存不足
CLR卸载AppDomain
CLR关闭

每个Finalize方法大约有2秒时间返回,所有Finalize方法40秒钟进行返回。如果超时,CLR会直接杀死进程。

#7 终结操作揭秘:

终结列表是由垃圾回收器控制的一个内部数据结构。列表中的每一项都指向一个对象--该对象定义了Finalize方法,在回收该对象的内存之前,应调用它的Finalize方法。

被判定为垃圾的对象,如果定义了Finalize方法,其终结列表中的指针将被移至freachable队列中。一个特殊的高优先级CLR线程负责调用Finalize方法。如果一个对象在freachable队列中,它就是可达的,不是垃圾。可终结的对象需要执行两次或以上(提升至另一代)垃圾回收才能释放他们占用的内存

#8 Dispose模式:强制对象清理资源:

public abstract class SafeHandleEx : IDisposable
{
    public void Dispose()
    {
        // 传递true,导致可以安全的访问引用其他对象的字段,因为其他这些对象的Finalize方法还没有调用
        this.Dispose(true);
    }

    public void Close()
    {
        // 传递true,导致可以安全的访问引用其他对象的字段,因为其他这些对象的Finalize方法还没有调用
        this.Dispose(true);
    }

    public ~SafeHandleEx()
    {
        // 传递false,导致不能安全的访问引用其他对象的字段,因为其他这些对象的Finalize方法可能已经调用
        this.Dispose(false);
    }

    protected virtual void Dispose(Boolean disposing)
    {
        if (disposing)
        {
            // 在该if语句中,可以安全的访问引用其他对象的字段,因为其他这些对象的Finalize方法还没有调用
        }

        GC.SuppressFinalize(this);
    }
}

#9 使用实现了Dispose模式的类型

public static void Go() {
    Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };
    FileStream fs = new FileStream("Temp.dat", FileMode.Create);
    fs.Write(bytesToWrite, 0, bytesToWrite.Length);

    // 显示关闭文件
    ((IDisposable)fs).Dispose();  
    // 必须调用Dispose之后调用该方法,否则报告文件正在被占用
File.Delete("Temp.dat");
}

确定必须清理资源时,确定可以安全的调用Dispose或Close,并希望将对象从终结表中删除,禁止对象提升从而提升性能时,才调用Dispose或Close。

#10 使用C# using语句

#11 StreamWriter对FileStream的依赖

#12 手动监视和控制对象的生存期(GCHandle)

转载于:https://www.cnblogs.com/thlzhf/archive/2012/12/06/2805491.html

相关文章:

  • 编码与解码
  • C#开发中碰到的问题------input file change 事件只执行一次
  • iframe自适应
  • Mac OS X 苹果电脑如何查什么程序在偷跑流量
  • SSH隧道技术简介:端口转发SOCKS代理
  • Lync Server 2013 标准版部署(一)AD准备
  • ECMALL综合手册
  • Android 7.1源码编译导入AS完整教程
  • 100-59
  • 只要这几步,webpack速成不是事儿
  • 百度天工携手康力电梯打造电梯物联网智能时代
  • 数据库表设计原则(多表字段尽量唯一)
  • Js中for in 和for of的区别
  • 观察转小写的操作-字符函数
  • 002_网上脚本执行请注意,慎重
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • Centos6.8 使用rpm安装mysql5.7
  • conda常用的命令
  • co模块的前端实现
  • Java方法详解
  • Redis的resp协议
  • 基于 Babel 的 npm 包最小化设置
  • 让你的分享飞起来——极光推出社会化分享组件
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 无服务器化是企业 IT 架构的未来吗?
  • 一文看透浏览器架构
  • elasticsearch-head插件安装
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • #{} 和 ${}区别
  • #Z2294. 打印树的直径
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (2)STM32单片机上位机
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (分布式缓存)Redis持久化
  • (分布式缓存)Redis分片集群
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (九)One-Wire总线-DS18B20
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • (转)ABI是什么
  • (最完美)小米手机6X的Usb调试模式在哪里打开的流程
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .net分布式压力测试工具(Beetle.DT)
  • .NET基础篇——反射的奥妙
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • [BIZ] - 1.金融交易系统特点
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)