Clr Via C#读书笔记---垃圾回收机制

#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)

时间: 2024-10-05 05:07:43

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

4.5-全栈Java笔记:垃圾回收机制

垃圾回收机制(Garbage  Collection) Java引入了垃圾回收机制,令C++程序员最头疼的内存管理问题迎刃而解.JAVA程序员可以将更多的精力放到业务逻辑上而不是内存管理工作上,大大的提高了开发效率. 垃圾回收原理和算法 1)内存管理 Java的内存管理很大程度指的就是对象的管理,其中包括对象空间的分配和释放. 对象空间的分配:使用new关键字创建对象即可 对象空间的释放:将对象赋值null即可.垃圾回收器将负责回收所有"不可达"对象的内存空间. 2)垃圾回收过程 任何

Java的垃圾回收机制笔记

Java的垃圾回收机制笔记 java垃圾回收的意义 确保不再被引用的对象的内存空间被回收. 确保被引用的对象的内存不被错误回收. 再分配内存. java垃圾回收的常用方法 引用计数收集器 堆中的每个对象(不是对象的引用)都有一个引用计数.当一个对象被创建时,给该对象分配一个变量,该变量计数设置设置为1.当任何其他变量被赋值为这个对象的引用,计数加1(a=b,则b引用的对象计数+1),但当一个对象的某个引用超过生命周期或者被设置为一个新值的时候,引用的计数减1(a=c,则a不再指向b指向的对象,而

垃圾回收机制GC知识再总结兼谈如何用好GC(其他信息: 内存不足)

来源 一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般有如下几种: 1.手动管理:C,C++ 2.计数管理:COM 3.自动管理:.NET,Java,PHP,GO- 但是,手动管理和计数管理的复杂性很容易产生以下典型问题: 1.程序员忘记去释放内存 2.应用程序访问已经释放的内存 产生的后果很严重,常见的如内存泄露.数据内容乱码,而且大部分时候,

你不得不知道的-垃圾回收机制及析构函数原理解析

前言 当学习到Web API中摸索原理时,对于其中有关垃圾回收只是有点印象并未深入去了解其原理并且对索引器用的也很少,所以利用放假期间好好回顾下已经忘记或者遗漏的知识,温故而知新大概就是这道理吧,虽然园子中关于这两者的文章也是多不胜数,但笔者也有自己独特的见解. 垃圾回收机制 引言 我们知道.NET Framework中的对象是创建在托管堆中的,但是像C.C++等其他底层语言中的对象是创建在非托管堆中的,所以在这类语言中就会出现编程人员忘记去释放已经没有用的对象,同时编程人员也可能会去试图访问已

垃圾回收机制GC知识再总结兼谈如何用好GC

一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般有如下几种: 1.手动管理:C,C++ 2.计数管理:COM 3.自动管理:.NET,Java,PHP,GO… 但是,手动管理和计数管理的复杂性很容易产生以下典型问题: 1.程序员忘记去释放内存 2.应用程序访问已经释放的内存 产生的后果很严重,常见的如内存泄露.数据内容乱码,而且大部分时候,程序的

Java和.NET中的垃圾回收机制比较

相同点: 都采用了分代的机制. 都支持并发GC. 都没有采用引用计数方式,而是采用了追踪技术. .NET中,可以通过代码GC.Collect() 强制要求CLR进行垃圾回收(由于垃圾回收是异步的,CLR有一个专用的线程负责垃圾回收,因此,即使调用GC.Collect,也并不是实时的调用了Finalize,因此要保证确实调用了析构方法,可以使用语句GC.WaitForPendingFinalizers()来确保析构方法真的被运行了,参考http://cnn237111.blog.51cto.com

.NET的垃圾回收机制

.NET的垃圾回收机制: CLR管理内存的区域主要有三块: 一: 线程的堆栈 ,用于分配值类型实例.堆栈主要有操作系统管理,不受垃圾收集器的控制,当值类型实例所在的方法结束时,其存储单位自动释放.栈的执行效率高,但存储容量有限. 二: GC堆,用于分配小对象实例.如果引用类型对象的实例小于85000字节,实例将被分配在GC堆上.当有内存分配或者回收时,垃圾收集器会对GC对进行压缩. 三: LOH(Large object heap),用于分配大对象实例.LOH堆不会被压缩,而且只有在完全GC回收

大数据基础篇----jvm的知识点归纳-5个区和垃圾回收机制

一直对jvm看了又忘,忘了又看的.今天做一个笔记整理存放在这里. 我们先看一下JVM的内存模型图: 上面有5个区,这5个区干嘛用的呢? 我们想象一个场景: 我们有一个class文件,里面有很多的类的定义是不是,类的定义放在什么地方呢?类的定义就放在方法区里面. 程序在运行时会产生很多的对象,比如LinkList等这些对象就存放在堆里面. 栈: 我们的程序在运行时本质上就是启动线程在运行,比如main函数就是一个主线程.一个main主线程可以有很多的子线程. 线程在工作就是执行我们的各种方法.方法

.Net 垃圾回收机制原理(一)

英文原文:Jeffrey Richter 编译:赵玉开 链接:http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html 有了Microsoft.Net clr中的垃圾回收机制程序员不需要再关注什么时候释放内存,释放内存这件事儿完全由GC做了,对程序员来说是透明的.尽管如此,作为一个.Net程序员很有必要理解垃圾回收是如何工作的.这篇文章我们就来看下.Net是如何分配和管理托管内存的,之后再一步一步描述垃圾回收器工作