阅读目录

  • 前言
  • 线程堆栈的分配
  • 托管堆上对象的分配
  • 结束语

前言

   .Net中的运行时,以及各个类型、对象、线程堆栈以及托管堆之间的关系,在初学者(俺是初学者中的菜鸟 J)看来,有很多是难以理解的东西,俺在看了CLR Via C# 的前几章后,现在将文中的大概意思并加以自己的理解,重现运行时,以及各个关系。希望各位尽量拍砖,多多指出不正确的地方,共同进步。

线程堆栈的分配

图1中展示了CLR加载的一个Microsoft Windows进程。在一个进程中,可能会存在多个线程。在创建一个线程时,这个线程会分配到一个1MB大小的堆栈。这个堆栈空间的作用:用于向方法传递 实参,并用于存储在方法内部定义的局部变量。图1展示了一个线程的堆栈(右侧)。堆栈都是从高位内存向地位内存地址构建。在左侧图中,该线程执行了一些代 码,它的堆栈上已经有一些数据(右图上半部分灰色区域)。现在假定线程要执行M1方法。

在一个方法中,应该包含一些开场白代码,负责在方法开始前对变量进行初始化操作,以及一些收场白代码,负责方法执行完毕之后进行清理工作,以便返回调用者。当M1方法开始执行时,它的开场白代码在线程的对战中为局部变量name分配内存,如图2所示:

接 着,M1中的代码执行,调用M2方法,将局部变量name作为一个实参来传递。这造成name局部变量中的地址被压入堆栈。在M2方法内部,将使用名为s 的形参变量来标识堆栈位置(注意,有的架构通过寄存器来传递实参以提升性能,但这对于当前的讨论来说并不重要)。另外,在调用一个方法时,还会将一个“返 回地址”压入堆栈。以便被调用的方法在完成之后,应该返回到这个位置。参见图3:

M2方法开始执行时,他的开场白代码在线程的堆栈中为局部变量length 和tally分配内存,如图4。然后,开始执行M2方法内部的代码。最终,M2会执行到return语句,这时CPU执行指针会被设置成堆栈中刚才存储的[返回地址] ,而 且M2的堆栈帧会进行辗转开解(unwind)(个人大概理解意思是:释放M2的内部局部变量),然后堆栈内部会恢复到图2状态,之后,M1将继续执行后 面代码,最终M1也会返回到它的调用者,这个过程其实跟M2是一样的,M1执行完成之后,M1的堆栈帧会进行辗转开解,恢复成图1所示那样。跟着会执行 M1后续的代码。图4:

托管堆上对象的分配

         讨论完了堆栈上的内存分配之后,我们再来看下托管堆上对象的分配。我们知道在.Net中值类型是存储在堆栈上,引用类型是存储在托管堆上,上面线程堆栈的分配中,name是string类型,属于引用类型,string的分配属于比较特殊的部分,这里我推荐:

Artech的大作:字符串的驻留(String Interning)

Anytao的大作:[你必须知道的.Net]第九回:品味类型—值类型与引用类型(中)—规则无边

说明:在涛哥的这篇文章中,建议多看看精彩的评论。

现在,假定有以下两个类定义如下:

// Employee 类定义internal class Employee{

public int GetYearEmployed(){…}

public virtual String GetProgressReport(){…}

public static Employee Lookup(String name)(){…}

}

//  Manager类定义  继承自 Employeeinternal sealed class Manager:Employee{

public override String GetProgressReport(){…}

}

现在Windows进程已经启动,CLR已经加载完成,托管对已初始化,而且已经创建好了一个线程连同他的1MB的堆栈控件。该线程已经执行了一些代码, 现在马上就要调用M3代码。图5展示了当前的状况。M3方法包含的代码演示了CLR是如何工作的。我们平时不会写这样的代码,因为它们实际上并不做任何有 用的事情。图5:

当JIT编译器将M3的IL代码转换成本地CPU指令时,会注意到M3内部引用的所有类型:Employee,Int32,Manager 以及String(因为有“Joe”) 。这时,CLR要确保定义了这些类型的所有程序集已经加载到AppDomain中。然后,利用程序集的元数据,CLR提取有关这些类型的信息,并创建一些 数据结构来表示类型本身。图6展示了用于Employee 和Manager类型对象的数据结构。由于线程之前已经执行了一些代码,所以不妨假设int和String的类型对象已经创建好了,所以图中没有显示它 们。图6:

现在我们来讨论下这些类型对象。在创建对象的时候,所有对象除了包含实例成员外,都会再包含两个额外的成员:类型对象指针同步块索引。 从上图中可以看出,Employee和Manager类型对象都有这两个成员。定义一个类型时,可以在类型的内部定义静态数据字段。为这些静态数据字段提 供支援的字节是在类型对象自身中分配的。在每个类型对象中,最后都包含一个方法表。在方法表中,类型中定义的每个方法都有一个对应的纪录项。

Employee 定义了三个方法,所以在它的方法表中有三个纪录项。同理,Manager只定义了一个方法,所以在Manager的方法表中只有一个纪录项。

现在,当CLR确定方法需要的所有类型对象都已创建,而且M3的代码已经编译后,就允许线程开始执行M3的本地代码。M3的开场白代码执行时,必须从线程 的堆栈中为局部变量分配内存(引用类型存储引用,引用指向对象所在托管堆的偏移地址,此时尚未在托管堆创建对象,所以会赋值为null,在用new新增对 象后,才会指向新对象的引用地址,值类型存储变量本身,),在图中代码中,CLR会自动将所有局部变量初始化为null或0。图7:

然后M3继续执行代码,紧接着构造一个Manager对象,这个构造操作会在托管堆中创建Manager类型的一个实例,可以看出,和所有对象一 样,Manager对象也有一个类型对象指针和同步块索引。该对象还包含容纳Manager类型定义的所有实例数据字段及其任何基类定义的所有实例字段所 需的字节。任何时候在堆上新建一个对象,CLR都会自动初始化内部类型对象指针成员,让它引用与对象对应的类型对象(本例就是Manager类型对象)。 此外,CLR还会首先初始化同步块索引,并将对象的所有实例字段设为null或0,然后才会调用类型构造器(可能会修改某些实力字段),随后 new操作符会返回新建的Manager对象的内存地址,该地址保存在变量e中(e在线程的堆栈上)。图8:

紧 接着M3的下一行代码调用Employee的静态方法Lookup。在调用一个静态方法时,CLR会定位与定义静态方法的类型对应的类型对象。然 后,CLR在类型对象的方法表中定位引用了被调用方法的纪录项,然后对方法进行JIT编译(如果需要的话),最后调用JIT编译过的代码。就本例来说,我 们假定Employee的Lookup方法要查询一个数据库来查找Joe。这里Lookup是返回一个Employee类型的对象。假定数据库指出Joe 是公司的一名经理,所以在内部,Lookup方法在堆上构造一个新的Manager对象,初始化它,然后返回该对象的地址,这个地址保存到局部变量e中。 操作的结果如图9所示:

注意,此时,e不再引用创建的第一个Manager对象。事实上,由于没有变量引用这个对象,所以它是将来进行垃圾收集时的主要候选对象。

M3的下一行代码调用Employee的非虚实例方法GetYearEmployed。在调用一个非虚实例方法时,CLR会找到与发出调用的变量的的类型 对应的类型对象。在本例中,e被定义成一个Employee(假如Employee类型没有定义这个方法,则会回溯类层次结构,在基类查找)。然 后,CLR在类型对象的方法表中找到引用了被调用方法的纪录项,对方法进行JIT编译,然后调用JIT编译过的代码,就本例来说,假定该方法返回5,这个 整数保存在year中。结果如图10:

M3的下一行代码调用Employee的虚实例方法GetProgressReport。在调用一个虚实例方法时,CLR要做一些额外的工作。首先,它在 发出调用的变量中查找,然后跟随地址到发出调用的对象。在本例中,变量e指向Joe 的Manager对象。然后,CLR会检查对象的内部类型对象指针成员,这个成员引用了对象的实际类型。然后,CLR在类型对象的方法表中定位引用了被调 用方法的纪录项,对方法进行JIT编译,然后调用编译后的代码,就本例来说,会调用Manager的GetProgressReport实现,因为e引用 的是一个Manager对象,操作结果如图11:

注意,如果Employee的Lookup方法发现Joe只是一个Employee,而不是一个Manager,Lookup会在内部构造一个 Employee对象,它的类型对象指针将引用Employee类型对象。这样一类,最终执行的就是Emplouee的 GetProgressReport实现,而不是Manager的GetProgressReport实现。

至此,已经讨论了源代码、IL和JIT编译的代码之间的关系,还讨论了线程的对战、参数、局部变量、以及如何引用托管堆上的对象。我们还知道对象中包含一个指针,它指向对象的类型对象(类型对象中包含静态字段、方法表、类型对象指针和同步块索引)。还讨论了CLR如何调用静态方法、非虚方法以及虚实例方法。理解这些后,可以深刻认识到CLR的工作方式。这些知识会带来很大的帮助。

接下来再更深层一点,看看CLR内部发生的事情。从前面几个图中,我们可以看到Employee和Manager创建类型对象时,必须初始化这些成员。那 么,具体初始化什么呢?CLR开始在一个进程中运行时,它会立即为System.Type类型创建一个特殊的类型对象。Employee和Manager 类型对象是该类型的实例。因此,他们的类型对象指针会初始化成Type类型对象的引用。如图12:

当然,System.Type类型对象本身也是一个对象,所以内部也同样包含一个类型对象指针成员。那么这个指针指向谁呢?它指向它本身。因为System.Type类型对象本身是一个类型对象的实例。

结束语

现在,我们总算理解了CLR的整个类型对象及其工作方式,System.Object的GetType方法返回的是存储在指定对象的“类型对象指针”,这样,就可以判断系统中任何对象(包括类型对象)的真实类型。

转载于:https://www.cnblogs.com/zzunstu/p/3408949.html

.Net运行时的相互关系相关推荐

  1. C#运行时的相互关系

    C#运行时的相互关系 本博客主要讲述运行时类型.对象.线程栈和托管堆之间的相互关系,静态方法.实例方法和虚方法的区别,以及内存的分配和回收. 线程栈:在一个进程中可能包含多个线程,一个线程在创建的时候 ...

  2. C# (类型、对象、线程栈和托管堆)在运行时的相互关系

    在介绍运行时的关系之前,先从一些计算机基础只是入手,如下图: 该图展示了已加载CLR的一个windows进程,该进程可能有多个线程,线程创建时会分配到1MB的栈空间.栈空间用于向方法传递实参,方法定义 ...

  3. 类型,对象,线程栈和托管堆在运行时的相互关系(一)。

    当系统加载一个CLR的进程,进程里面可能有多个线程,这时候系统会给这个进程创建一个大小为1M的线程栈.这个线程栈用来存放方法调用的实参,和方法内部定义的局部变量.下图展示了一个线程栈的栈内存.线程栈的 ...

  4. [CLR via C#]4. 类型基础及类型、对象、栈和堆运行时的相互联系

    原文:[CLR via C#]4. 类型基础及类型.对象.栈和堆运行时的相互联系 CLR要求所有类型最终都要从System.Object派生.也就是所,下面的两个定义是完全相同的, //隐式派生自Sy ...

  5. 通过企业分布式缓存共享运行时数据

    许多企业都结合使用 Microsoft .NET Framework 和 Java 应用程序,尤其是那些出于各种考虑不能只依赖于单一技术的大中型企业. 通常,企业采用 Web 应用程序.面向服务的体系 ...

  6. 1.Containerd容器运行时初识与尝试

    0x00 前言简述 1.基础介绍 2.专业术语 3.架构简述 0x01 安装配置 1.Ubuntu安装Containerd.io流程 0x02 简单使用 1.镜像拉取与运行 2.创建和使用网络 3.与 ...

  7. java编译时多态和运行时多态_运行时多态、编译时多态和重载、重写的关系(不区分Java和C#,保证能看懂!)...

    以前在大学学习OOP的时候,知道了重载和重写的区别,但如果要把他们和多态联系起来,我想很多新手朋友和我当初一样是死记的,可是时间长了,自然而然就忘记了,最近在写测试的时候,终于"开窍&quo ...

  8. 【容器运行时】一文理解 OCI、runc、containerd、docker、shim进程、cri、kubelet 之间的关系

    参考 docker,containerd,runc,docker-shim 之间的关系 Containerd shim 进程 PPID 之谜 内核大神教你从 Linux 进程的角度看 Docker R ...

  9. Java判断数组是递增或者递减_账户对应关系,是指采用复式记账法对每笔交易或事项进行记录时,相关账户之间形成的增加或减少的相互关系。...

    [多选题]下列不属于会计信息质量要求的有 ( ) [判断题]共价键的键长等于成键原子共价半径之和. [多选题]关于do while循环,下列说法正确的是(). [单选题]资产按照预计从其持续使用和最终 ...

最新文章

  1. java 线程 连接池_java程序实现线程连接池功能
  2. C# 反射与dynamic最佳组合
  3. 访问vue实例中的数据
  4. 从比特大陆AI芯片入局智慧城市看阿里腾讯的城市“攻坚战”...
  5. 安装maven到Eclipse
  6. 【最全最详细】publiccms实现将公共部分提取成单独模块引入
  7. Struts1中execute实现过滤控制
  8. 产品经理学习总结(1)——人人都是产品经理之需求文档语法
  9. install texlive-full on ubuntu
  10. nopcommerce插件深度剖析
  11. 数据结构上机实践第八周项目9-广义表算法库及应用
  12. Spring Boot –无法确定数据库类型NONE的嵌入式数据库驱动程序类
  13. Spark修炼之道(高级篇)——Spark源码阅读:第八节 Task执行
  14. 在 Delphi 下使用 DirectSound (1): 枚举播放设备
  15. SR 学习记录----JUNOS为例
  16. 4173: 数学 欧拉函数 思路题
  17. 未来的计算机 展望未来作文,展望未来作文素材_2020展望未来作文精选5篇
  18. 国内超强JS框架正在开源免费申请中
  19. DuckDuckGo
  20. 单身的程序猿伤不起,在神棍节感慨下

热门文章

  1. Elasticsearch 及 Kibana 安装篇
  2. Qtum量子链应邀出席2019棉兰区块链沙龙进军东南亚市场第一站
  3. 对DataSet,DataRow,DateTable转换成相应的模型
  4. JavaScript之浅复制【拷贝】与深复制【拷贝】【二】
  5. 书写神器——markdown
  6. 国家“十三五”重点出版规划获批
  7. 打开excel发送错误报告
  8. 中国@代码生成技术@国产
  9. Python爬虫(六)_Requests的使用
  10. python爬虫(四)_urllib2:handle处理器和自定义opener