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

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

1)调用IL指令newobj,为代表资源的类型分配内存。在C#中使用new操作符,编译器就会自动生成该指令。
2)初始化内存,设置资源的初始状态,使资源可用。类型的实例构造器负责设置该初始状态。
3)访问类型的成员(可根据需要反复)来使用资源。
4)摧毁资源的状态以进行清理。正确清理资源的代码要放在FinalizeDisposeClose方法。
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)

Clr Via C#读书笔记---垃圾回收机制的更多相关文章

  1. java程序性能优化读书笔记-垃圾回收

    衡量系统性能的点 执行速度:即响应时间 内存分配:内存分配是否合理,是否过多消耗内存或者存在内存泄露 启动时间:程序从启动到正常处理业务需要的时间 负载承受能力:当系统压力上升,系统执行速度和响应时间 ...

  2. Java虚拟机学习笔记——JVM垃圾回收机制

    Java虚拟机学习笔记——JVM垃圾回收机制 Java垃圾回收基于虚拟机的自动内存管理机制,我们不需要为每一个对象进行释放内存,不容易发生内存泄漏和内存溢出问题. 但是自动内存管理机制不是万能药,我们 ...

  3. 一文带你吃透CLR垃圾回收机制

    前言 今天我们来共同学习一下CLR的垃圾回收机制,这对我们写出健壮性的代码很有帮助,也许有人会认为多此一举,认为垃圾回收交给CLR就行,我不用关心这个,诚然,大多数情况下是这样的,但是,我们今天讨论的 ...

  4. Javascript垃圾回收机制(学习笔记)

    1,javascript具有自动的垃圾回收机制,自动内存的分配和无用内存的回收都可以自动管理.垃圾回收器周期性的执行: 2,Javascript的垃圾回收策略分为:引用计数和标记清除: 2.1 标记清 ...

  5. Javascript高级编程学习笔记(11)—— 垃圾回收机制

    垃圾回收机制 垃圾回收机制,是保证脚本能长时间运行的重要机制 JS具有自动垃圾收集机制,也就是说执行环境会负责管理代码执行过程中使用的内存 与一些偏底层的语言(c.c++)不同,我们不需要手工地去管理 ...

  6. Java精选笔记_面向对象(包、访问控制、内存机制、垃圾回收机制)

    包 包的定义与使用 专门用来存放类的,通常功能相同的类存放在相同的包中. 包的声明只能位于Java源文件的第一行 Java语言中的常用包 java.lang:包含Java语言的核心类,如String. ...

  7. 【Java学习笔记】Java的垃圾回收机制

    搬以前写的博客[2014-12-30 15:07] 以前很少关注内存的问题,基本没有关注,这方面的小白,原因在于自己都是写的自我娱乐的小程序,不关注性能,不是提供服务.而企业级别的应用在程序稳健性方面 ...

  8. JavaScript--我发现,原来你是这样的JS(四)(看看变量,作用域,垃圾回收机制是啥)

    一.介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第四篇,是红宝书第四章内容(主要是变量和作用域问题),当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的 ...

  9. JS--我发现,原来你是这样的JS(四)(看看变量,作用域,垃圾回收机制是啥)

    一.介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第四篇,是红宝书第四章内容(主要是变量和作用域问题),当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的 ...

随机推荐

  1. 【转】Intel RealSense(实感技术)概览

    Intel RealSense(实感技术)概览 1 Reply 版权声明:本文系本站作者自己翻译整理,欢迎转载,但转载请以超链接形式注明文章来源(planckscale.info).作者信息和本声明, ...

  2. OpenCV摄像头人脸识别

    注: 从外设摄像装置中获取图像帧,把每帧的图片与人脸特征进行匹配,用方框框住识别出来的人脸 需要用到的函数: CvHaarClassifierCascade* cvLoadHaarClassifier ...

  3. PHP微信支付开发实例

    这篇文章主要为大家详细介绍了PHP微信支付开发过程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 PHP微信支付开发过程,分享给大家,供大家参考,具体内容如下 1.开发环境 Thinkphp 3. ...

  4. 20 BasicTaskScheduler0 基本任务调度类基类(二)——Live555源码阅读(一)任务调度相关类

    这是Live555源码阅读的第二部分,包括了任务调度相关的三个类.任务调度是Live555源码中很重要的部分. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/ol ...

  5. ajax给全局变量赋值问题解决

    $.ajax({ type: "post", //以post方式与后台沟通 url: "./php/chartAjax.php", //与此php页面沟通 da ...

  6. 安装rabbitmq以及python调用rabbitmq--暂欠

    一.安装erlang yum install erlang 二.安装rabbitmq rpm包: wget http://www.rabbitmq.com/releases/rabbitmq-serv ...

  7. bootstrap 响应式布局

    先上点媒体查询css(某个著名的”段子“),跟bootstrap无关: <html> <head> <style> body { background-color: ...

  8. Python分割list

    对于一个很大的列表,例如有超过一万个元素的列表,假如需要对列表中的每一个元素都进行一个复杂且耗时的计算,用单线程处理起来会很慢,这时有必要利用多线程进行处理,处理之前首先需要对大的列表进行分割,分割成 ...

  9. 【Networking】Thrift and gRPC

    参考资料: http://doc.oschina.net/grpc?t=60138 http://www.grpc.io/ https://thrift.apache.org/ https://git ...

  10. sharepoint 2010 页面刷新时滚动条位置保持不变 Controlling scrollbar position on postback

    sharepoint 2010 页面刷新时滚动条位置保持不变 Controlling scrollbar position on postback在sharepoint 2010中,如果当前页面的篇幅 ...