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

→首先运行应用程序,创建一个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. Windows运行命令

    winver---------检查Windows版本 wmimgmt.msc----打开windows管理体系结构 wupdmgr--------windows更新程序 winver--------- ...

  2. retrying模块的学习

    retrying模块的学习 我们在写爬虫的过程中,经常遇到爬取失败的情况,这个时候我们一般会通过try块去进行重试,但是每次都写那么一堆try块,真的是太麻烦,所以今天就来说一个比较pythonic的 ...

  3. cefsharp保存文件为pdf

    var success = await browserViewModel.WebBrowser.PrintToPdfAsync(dialog.FileName, new PdfPrintSetting ...

  4. SOAP简单示例

    看了网上的几个文章,SOAP的示例布局都不清晰,不能马上入手,特意写个例子与大家分享,同时记录备用. 当前环境:VS2013 + WPF private void Button_Click(objec ...

  5. Video.js 简单的使用介绍

    vedio.js 是一款视频播放插件,它会自动检测浏览器对 HTML5 的支持情况,如果不支持 HTML5 则自动使用 Flash 播放器.下面来介绍下它的使用: 引用video-js.cs样式文件和 ...

  6. Asp.Net Mvc5 结合 SignalR2.0+ 实现消息交互简单例子

    Nuget添加 SignalR包 1.构建一个MyHubServer服务端类 定义类的特性标签为 [HubName("chatHub")] public class MyHubSe ...

  7. 将prometheus采集的数据远程存储到influxdb中

    这个比较简单, https://docs.influxdata.com/influxdb/v1.7/supported_protocols/prometheus 只需要更改prometheus.yam ...

  8. Orchard学习 02、orchard 路由

    Orchard对mvc路由重新做了包装,重写了asp.net的路由模块 一.路由模块类图 1.路由 Descriptor RouteDescriptor是对常规mvc路由的包装类,它的Route属性就 ...

  9. MIT-6.828-JOS-lab5:File system, Spawn and Shell

    Lab 5: File system, Spawn and Shell tags: mit-6.828 os 概述 本lab将实现JOS的文件系统,只要包括如下四部分: 引入一个文件系统进程(FS进程 ...

  10. rabbitmq学习(三) —— 工作队列

    工作队列,又称任务队列,主要思想是避免立即执行资源密集型任务,并且必须等待完成.相反地,我们进行任务调度,我们将一个任务封装成一个消息,并将其发送到队列.工作进行在后台运行不断的从队列中取出任务然后执 ...