.NET对象的创建、垃圾回收、非托管资源的手动处理

本篇用来梳理对象的创建、垃圾的回收,以及非托管资源的手动处理。

→首先运行应用程序,创建一个Windows进程。

→CLR创建一块连续的虚拟地址空间,这个地址空间就是托管堆。而且,这个地址空间最初并没有对应的物理存储空间。

虚拟地址空间分成2段。一个区段是普通堆,也叫GC堆,大小小于85000字节的引用类型对象的实例被分配在这里;另一个是大对象堆,大小大于等于85000字节的引用类型对象的实例被分配在这里。

对于客户端应用程序,每个区段的大小大致是16MB;对于服务端应用程序,每个区段的大小大致是64MB。另外,每个区段的大小还会因CPU的数量,是否是32位或64位操作系统而各异。

随着每个区段被装满对象,CLR会分配更多的区段,直到整个进程空间都满了为止,每个进程可使用4GB的内存。

托管堆上维护着一个指针NextObjPtr,指向下一个对象在托管堆上分配的位置。

托管堆根据存储信息的不同分为垃圾回收堆GC Heap和加载堆Loader Heap。垃圾回收堆GC Heap存储对象实例,受GC管理;加载堆Loader Heap存储AppDomain中的元数据信息,例如基类型、静态字段、静态方法、接口信息等,不受GC管理,它的生命周期从创建AppDomain开始到卸载AppDomain结束。此时,托管堆以及其它方面大致是这样分布的:

→在托管堆上创建对象

□ 引用类型对象创建

使用new关键字创建引用类型对象。

FileStream fs = new FileStream(@"",FileMode.Open);

以上代码经编译器编译,在生成的中间IL代码中实际上是一个newobj指令。IL相关的指令还包括:ldstr指令用于创建string类型对象,newarr用于分配新的数组对象,box指令用于在值类型转换为引用类型时,将值类型字段拷贝到托管堆上。

通过以下代码来体会托管堆上创建对象的大致过程:

public class Employee
{
    private int _id;
    private Status _status;
    public Employee()
    {
        _id = 1;
        _status = new Status();
    }
}

public class Sales : Employee
{
    public bool _isLive;

    public bool IsLive()
    {
        return _isLive;
    }

    public static void Main()
    {
        Sales _sales;
        _sales = new Sales();
        _sales._isLive = true;
        Console.WriteLine(_sales.IsLive());
    }
}

public class Status
{
    private int _years;
    private char _level = "A";
}


1、执行Sales _sales,在线程堆栈上开辟4byte的内存空间,用于保存Sales对象的托管堆地址,此时为null。

2、_sales = new Sales(),递归计算Sales实例对象的字节总数,递归从Sales本身开始,到父类Employee,一直到基类System.object,具体字节计算过程如下:

Sales对象实例字节数=字段_isLive的字节数为1byte
Sales对象实例的附加成员TypeHandler字节数=4byte
Sales对象实例的附加成员SyncBlockIndex字节数=4byte
Sales对象实例的父类Employee字节数=字段_id的字节数4byte + 字段_status保存指向Status对象实例的引用为4byte

1+4+4+4+4=17byte,考虑到内存块总是按照4byte进行内存对齐,因此补齐到20byte。

由于在Sales的父类Employee的构造函数中,实例化了一个Status类,所以还需要计算Status对象实例的字节数:
Status对象实例字节数=字段_years的字节数4byte + 字段_level的字节数2byte
Status对象实例的附加成员TypeHandler字节数=4byte
Status对象实例的附加成员SyncBlockIndex字节数=4byte
4+1+4+4=13byte,考虑到内存块总是按照4byte进行内存对齐,因此补齐到16byte。

综上,创建Sales对象实例所需总字节数为36。

3、在托管堆上向高地址扩展出36byte的连续空间,并为其分配内存地址。

4、进行Sales对象实例的初始化工作。

a:构造Sales类型的Type对象,并分配到托管堆中的Loader Heap上。
b:初始化Sales对象实例的2个附加成员:TypeHandler和SyncBlockIndex。TypeHandler指向Load Heap上的Method Table,SyncBlockIndex指向Synchronization Block内存块,用于在多线程环境下对实例对象的同步操作。
c:实例字段的初始化,先初始化System.Object的实例字段,然后是Employee,最后是Sales的实例字段。

5、将Sales实例对象的托管堆地址赋值给线程栈变量_sales。

□ 值类型对象创建

对于值类型(不包括类中值类型实例成员),比如struct,通过new关键字创建,实际上是在线程栈上创建了对象,线程堆栈不受CLR的垃圾回收控制,而是由操作系统直接管理,当值类型实例所在方法结束时,其存储单位被自动释放。在线程栈上,操作系统维护者一个堆栈指针,指向下一个自由空间的地址。

线程堆栈的内存地址是由高位向低位填充,也就是入栈时由高地址向低地址扩展,出栈时由低地址向高地址删除内存。通过以下代码来走一遍整个过程:

public void SaySth()
{
    int a = 1;
    char b = ‘x‘;
}

1、方法执行之前,线程堆栈指针指向一个高位,比如100
2、开始执行SaySth方法,SaySth方法的返回地址首先入栈,线程堆栈指针指向一个较低位置,比如99
3、执行int a = 1,在线程堆栈上分配4byte的内存空间,将值1保存在该地址空间内,线程堆栈指针向下移动4个字节指向一个较低位置,比如98
4、执行char b = ‘x‘,在线程堆栈上分配2byte的内存空间,将值"x"保存在该地址空间内,线程堆栈指针向下移动4个字节指向一个较低位置,比如97
5、方法执行结束,依次上次b和a的内存,线程堆栈指针回到返回地址,即99

→垃圾回收

在托管堆上,设计了一个叫作"GC roots",这些内存空间总是可到达的,存储着对象实例的引用,"GC roots"中的这些对象被标记为"live",所有被"GC root"引用的对象实例也被标记为"live",GC会按照这种方式一直循环遍历下去,直到某个对象实例没有引用对象实例。这就好像一颗"节点树",从根节点开始遍历,一直到没有子节点的节点循环遍历才结束。

在垃圾回收的时候,所有没有被标记为"live"的对象实例都会回收,然后托管堆的内存空间会重新压缩,托管堆低位的内存空间准备迎接下一批实例对象。

"GC root"有不同的类型:
● 当前方法内的局部变量被视作"GC root"
● 静态变量也被视作"GC root"
● 如果一个托管对象实例需要被传递给COM+库,也会被视作"GC root"
● 如果一个对象有析构器,在垃圾回收的时候是不会被回收的,只有调用finalizer的时候才对该对象进行回收,也就是说,带有析构器的对象也被视作"GC root"

为了提高垃圾回收的效率,GC引入了一个"代龄"策略,将托管堆中的对象分为三代,分别为0,1和2。而且会为不同的代龄设置不同的阙值容量,第0代大约256KB,第1代大约2MB,第2代大约10MB。被新建的对象起初都是第0代,GC也总是回收第0代没有被标记为"live"的对象实例。具体运作过程如下:

1、CLR初始化时,所有被添加到托管堆中的对象为第0代。
2、当有垃圾回收时,未被回收的对象代龄提升1级,变为第1代。
3、第1代对象如果没有达到阙值容量,就不会被回收,这样有效地提高了垃圾回收的效率。
4、仅当第0代阙值容量不足以创建新的对象,同时第1代对象实例的大小超出了第1代阙值容量,GC会同时回收第0代和第1代的对象,还未被回收的第1代对象升级为2代对象,还未被回收的第0代对象升级为1代对象。
5、如此反复。

→非托管资源的清理

对于一些托管资源,CLR的垃圾回收器能帮我们自动清理内存,而对于一些非托管资源,比如数据库连接、文件句柄、COM对象等,仍然需要开发者手动清理。具体请参考这里

时间: 2024-08-04 14:56:30

.NET对象的创建、垃圾回收、非托管资源的手动处理的相关文章

C#编程(七十四)----------释放非托管资源

释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.net运行库在合适的调用垃圾回收器进行回收. 非托管资源指的是.net不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize

C# 托管资源和非托管资源

托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法.默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源. C

[转]在C#中使用托管资源和非托管资源的区别,以及怎样手动释放非托管资源:

托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法.默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源. 在

.NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

在" .NET的堆和栈01,基本概念.值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配.我们知道:当执行一个方法的时候,值类型实例会在"栈"上分配内存,而引用类型实例会在"堆"上分配内存,当方法执行完毕,"栈"上的实例由操作系统自动释放,"堆"上的实例由.NET Framework的GC进行回收. 在" .NET的堆和栈02,值类型和

c# -- 对象销毁和垃圾回收

有些对象需要显示地销毁代码来释放资源,比如打开的文件资源,锁,操作系统句柄和非托管对象.在.NET中,这就是所谓的对象销毁,它通过IDisposal接口来实现.不再使用的对象所占用的内存管理,必须在某个时候回收:这个被称为无用单元收集的功能由CLR执行. 对象销毁和垃圾回收的区别在于:对象销毁通常是明确的策动:而垃圾回收完全是自动地.换句话说,程序员负责释放文件句柄,锁,以及操作系统资源:而CLR负责释放内存. 本章将讨论对象销毁和垃圾回收,还描述了C#处理销毁的一个备选方案--Finalize

说说非托管资源的回收

释放未托管的资源有两种方法 1.析构函数 2.实现System.IDisposable接口 一.析构函数 构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数.析构函数初看起来似乎是放置释放未托管资源.执行一般清理操作的代码的最佳地方.但是,事情并不是如此简单.由于垃圾回收器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了. 实例 C# 代码   复

.net非托管资源的回收

释放未托管的资源有两种方法 1.析构函数 2.实现System.IDisposable接口 一.析构函数 构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数.析构函数初看起来似乎是放置释放未托管资源.执行一般清理操作的代码的最佳地方.但是,事情并不是如此简单.由于垃圾回收器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了. 实例 C# 代码   复

利用IDisposable接口构建包含非托管资源对象

托管资源与非托管资源 在.net中,对象使用的资源分为两种:托管资源与非托管资源.托管资源由CLR进行管理,不需要开发人员去人工进行控制,.NET中托管资源主要指"对象在堆中的内存":非托管资源指对象使用到的一些托管内存之外的内资源(例如操作系统的资源),CLR不会管理这些资源,需要开发人员去控制..NET对象使用到的非托管资源主要有I/O流.数据库连接.Socket连接.窗口句柄等直接与操作系统操作的相关资源. 管理非托管资源 当一个对象不再使用时,我们应该将它使用的非托管资源释放掉

托管资源和非托管资源

在.net 编程环境中,系统的资源分为托管资源和非托管资源. 对于托管的资源的回收工作,是不需要人工干预回收的,而且你也无法干预他们的回收,所能够做的只是了解.net CLR如何做这些操作.也就是说对于您的应用程序创建的大多数对象,可以依靠 .NET Framework 的垃圾回收器隐式地执行所有必要的内存管理任务.        资源分为两种,托管的内存资源,这是不需要我们操心的,系统已经为我们进行管理了:那么对于非托管的资源,这里再重申一下,就是Stream,数据库的连接,GDI+的相关对象