#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. 修复 VirtualBox 下 Ubuntu 14.10 屏幕分辨率问题

    在 Windows 7 下使用 VirtualBox 安装了一个 Ubuntu 14.10 后,碰到了一个 640×480 屏幕分辨率的问题. 在 ‘Display Settings' 设置界面的 ‘ ...

  2. ionic中返回上一页

    .controller('NewsCtrl', ["$scope", "$ionicHistory", "$http","$tim ...

  3. [HDU2089]不要62

    [HDU2089]不要62 试题描述 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就 ...

  4. 解压版MySQL安装说明

    一.复制my.ini到MySQL解压的目录 例如:E:\MySQL 二.修改my.ini第39~40行 basedir = "E:\\MySQL" datadir = " ...

  5. 剑指Offer 二叉搜索树的后序遍历序列

    题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同.   思路: 后续遍历数组的尾部为根节点,前面的部分 ...

  6. php搜索分页

    最近做搜索分页的时候,发现第一页显示正常,点击到下一页的时候,显示结果变成了搜索全部内容. 仔细查看代码,发现当第一次输入关键词,提交到查询控制器的时候,表单提交的关键字不为空,可是点击到下一页的时候 ...

  7. github 多个项目共用同一个key的方法

    后面的项目不用添加ssh keys, 直接在项目下设置合作者(Collaborators), 搜索出去加进去即可免密码传代码.

  8. backbone模型层浅析

    Model层有两个类: Model, Collection 1.Model 不翻文档,我们用代码说话. 首先分析下类. var myM = Backbone.Model.extend({})//构造一 ...

  9. Git – Fast Forward 和 no fast foward

    Git 很是强大,在体验过rebase的华丽之后,再次发现之前在TFS上遇到的问题一下都有解了.但也印证了Git深入并非易事.这篇就谈下一个容易迷糊的概念:Fast forward. Fast-For ...

  10. [转]IntelliJ Idea 常用快捷键 列表(实战终极总结!!!!)

    IntelliJ Idea 常用快捷键 列表(实战终极总结!!!!) ntelliJ Idea 常用快捷键 列表(实战终极总结!!!!) 1. -----------自动代码-------- 常用的有 ...