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

→首先运行应用程序,创建一个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对象等,仍然需要开发者手动清理。具体请参考这里

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

  1. .NET垃圾回收 – 非托管资源

    前面一篇文章介绍了垃圾回收的基本工作原理,垃圾回收器并不是可以管理内存中的所有资源.对于所有的托管资源都将有.NET垃圾回收机制来释放,但是,对于一些非托管资源,我们就需要自己编写代码来清理这类资源了 ...

  2. CLR回收非托管资源

    一.非托管资源 在<垃圾回收算法之引用计数算法>.<垃圾回收算法之引用跟踪算法>和<垃圾回收算法之引用跟踪算法>这3篇文章中,我们介绍了垃圾回收的一些基本概念和原理 ...

  3. 5.C#释放非托管资源1

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

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

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  5. 转/ C# 托管资源和非托管资源

    原文 对于这两个一直就是模模糊糊的,半知零解 托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,由.NET运行库在合适时调用垃圾回收器 ...

  6. C# 托管资源与非托管资源

    在.net 编程环境中,系统的资源分为托管资源和非托管资源. 托管资源: Net平台中,CLR为程序员提供了一种很好的内存管理机制,使得程序员在编写代码时不要显式的去释放自己使用的内存资源(这些在先前 ...

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

    托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回 ...

  8. C# 托管非托管资源释放

    1.C#几乎所有对象都为托管对象,不同点是有的对象封装了非托管资源. 2.C#大部分对象在进行垃圾回收时都可以回收,包括非托管资源,因为非托管资源都已经通过C#类进行了封装,会将非托管资源的释放放在析 ...

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

    托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回 ...

随机推荐

  1. csfb

    SELECT CSDBTOGSMALLSuccessRate,CSFBTOGSMMODelay,CSFBTOGSMMODropRate,CSFBTOGSMMOFRStartCount,CSFBTOGS ...

  2. 常用 Git 命令清单【转】

    转自:http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html 作者: 阮一峰 日期: 2015年12月 9日 我每天使用 Git ,但是 ...

  3. artDialog4.1.7 摘自网络

    jquery.artDialog.source.js学习 1 关键的对象关系 art = jQuery = $ function artDialog() {...} artDialog.fn = ar ...

  4. PyCharm 2018实现远程调试代码

    pycharm是一个非常强大的python开发工具,现在很多代码最终在线上跑的环境都是linux,而开发环境可能还是windows下开发,这就需要经常在linux上进行调试,或者在linux对代码进行 ...

  5. dede列表页调用

    {dede:list pagesize ='16'} <li class="item pull-left"> <a class="item-wrap&q ...

  6. Android Service总结06 之AIDL

    Android Service总结06 之AIDL 版本 版本说明 发布时间 发布人 V1.0 初始版本 2013-04-03 Skywang           1 AIDL介绍 AIDL,即And ...

  7. public private protect

    public 公有  使用public意味着声明public之后的成员对每个人都是可用的 private 私有  除非必须公开底层实现细目,否则就应该将所有的域指定为private protect 继 ...

  8. MySQL多线程备份工具mydumper

    mydumper是一个针对MySQL和Drizzle的高性能多线程的备份和恢复工具.此工具的开发人员分别来自MySQL.Fackbook.SkySQL公司,目前已经有一些大型产品业务测试并使用了该工具 ...

  9. springcloud hystrix 部分参数整理

    hystrix.command.default和hystrix.threadpool.default中的default为默认CommandKey Command Properties Executio ...

  10. 【洛谷】P4199 万径人踪灭

    题解 每种字符跑一遍FFT,得到\(i + j = k\)时匹配的个数(要÷2,对于相同位置的最后再加上 然后算出\(2^{cnt[k]}\)的和,最后再减去用mancher匹配出的连续回文子串的个数 ...