我们将讨论类型、对象、线程栈和托管堆在运行时的相互关系,假定有以下两个类定义:

internal class Employee
    {
        public int GetYearsEmployed()
        {
            return 1;
        }
        public virtual string GetProgressReport()
        {
            return "zhengwei";
        }
        public static Employee Lookup(string name)
        {
            Employee e = new Manager();
            return e;
        }
    }

internal sealed class Manager : Employee
    {
        public override string GetProgressReport()
        {
            return "overrid zhengwei";
        }
    }

Windows 进程已启动,CLR已加载到其中,托管堆已初始化,而且已创建一个线程(连同它的1MB栈空间)。线程已执行了一些代码,马上就要调用M3方法。图1展示了目前的状态。Main方法包含的代码演示了CLR是如何工作的。

JIT编译器将Main的IL代码转换成本机的CPU指令,会注意到Main内部引用的所有类型,包括Employee,Int32,Manager以及String。 这时CLR要确认定义了这些类型的所有程序集都已加载。然后,利用程序集的元数据,CLR提取与这些类型有关的信息,
创建一些数据结构来表示类型本身。图2展示了为Employee和Manager类型对象使用的数据结构。由于线程在调用Main前已执行了一些代码,所以Int32和string类型对象已经创建好了,图中没有显示它们。

    现在我们讨论一一这些类型对象,我们在堆上创建的对象都包含两个额外的成员:类型对象指针和同步块索引。图2中Employee和Manager类型对象都有这两个成员。定义类型时,可在类型内部定义静态数据字段。为这些静态数据字段提供支援的字节在类型对象自身中分配。每个类型对象最后都包含一个方法表。在方法表中。类型定义的每个方法都有对应的记录项。由于Employee定义了3个方法,所以有3个记录项。Manager类型只定义了1个方法,所以Manager的方法表只有1个记录项。
    当CLR确认方法需要的所有类型对象都已创建,Main的代码已经编译之后,就允许线程执行Main的本机代码。Main代码执行时必须在线程栈中为局部变量分配内存,如图3 所示CLR自动将所有局部变量初始化为NULL或0,然而,如果代码试图访问尚未显式初始化的局部变量,C#会报错。

    然后,Main执行代码构造一个Manager对象。这造成在托管堆创建Manager类型的一个实例,如图4所示,可以看出,和所有对象一样,Manager对象也有类型对象指针和同步块索引。该对象还包含必要的字节来容纳Manager类型定义的所有实例数据字段,以及容纳由Manager的任何基类(本例就是Employee和Object)定义的所有实例字段。任何时候在堆上新建对象,CLR都自动初始化内部的“类型对象指针”成员来引用和对象对应的类型对象(本例就是Manager类型对象)。此外,在调用类型的构造器之前,CLR会先初始化同步块索引,并将对象的所有实例字段设为null或0。new 操作符返回Manager对象的内存地址,该地址保存到变量e中(e在线程栈上)。
 

     Main的下一行代码调用Employee的静态方法Lookup。调用静态方法时,CLR会定位与定义静态方法的类型对应的类型对象。然后,JIT编译器在类型对象的方法表中查找与初调用方法对应的记录项,对方法进行JIT编译,再调用JIT编译好的代码。本例假定Employee的Lookup方法要查询数据库来查找joe,再假定数据库指出的joe是公司的一名经理,所以在内部,Lookup方法在堆上构造一个新的Manager对象,用joe的信息初始化它,返回该对象的地址。该地址保存到局部变量e 中。这个操作的结果如图5.
注意,e不再引用第一个Manager对象。事实上,由于没有变量引用该对象,所以它是未来垃圾回收的主要目标。垃圾回收机制将自动回收该对象占用的内存。
 

     Main调用Employee的非虚实例方法GetYearsEmployed。调用非虚实例方法时,JIT编译器会找到与“发出调用的那个变量(e)的类型(Employee)”对应的类型对象(Employee类型对象)。这时变量e初定义成一个Employee。如果Employee类型没有定义正在调用的那个方法,JIT编译器会回溯类层次结构(一直回溯至Object),并在沿途的每个类型中查找该方法。之所以能这样回溯,是因为每个类型对象都有一个字段引用了它的基类型,这个信息在图中没有显示。
     然后,JIT编译器在类型对象的方法表中查找引用了被调用方法的记录项,对方法进行JIT编译,再调用JIT编译好的代码。本例假定Employee的GetYearsEmployed方法返回1。这个整数保存到局部变量year中。如图6所示。

    Main调用Employee的虚实例方法GetProgressReport。调用虚实例方法时,JIT编译器要在方法中生成一些额外的代码。方法每次调用都会执行这些代码。这些代码首先检查发出调用的变量,并跟随地址来到发出调用的对象,然后,代码检查对象的内部的“类型对象批针成员”,该成员指向对象的实际类型。然后代码在类型对象的方法表中查找引用了被调用方法的记录项,对方法进行JIT编译,再调用JIT编译好的代码。由于目前e引用一个Manager对象,所以会调用Manager的GetProgressReport实现。
    至此,我们已经讨论了源代码、IL和JIT编译的代码之间的关系。还讨论了线程栈、实参、局部变量以及这些实参和变量如何引用托管堆上的对象。还知道对象含有一个指针指向对象的类型对象(类型对象中包含静态字段和方法表)。还讨论了JIT编译器如何决定静态方法、非虚实例方法以及实例方法的调用方式。理解这一切之后,可以深刻地认识CLR的工作方式。以后在建构、设计和实现类型、组件以及应用程序时,这些知识会带来帮助。
    下面,我们将深入控计一下CLR内部发生的事情。
    注意Employee和Manager类型对象都包含“类型对象指针”成员。这是由于类型对象本质上也是对象。CLR创建类型对象时,必须初始化这些成员。初始化成什么呢?你肯定会这样问。CLR开始在一个进程中运行时,会立即为MSCorLib.dll中定义的System.Type类型创建一个特殊的类型对象。Employee和Manager类型对象都是该类型的“实例”。因此,它们的类型对象指针成员会初始化成对System.Type类型对象的引用。如图7所示

    当然,System.Type类型对象本身也是对象,内部也有“类型对象指针”成员。这个指针指向什么?它指向本身,因为System.Type类型对象本身是一个类型对象的“实例”。现在,我们总算理解了CLR的整个类型系统及其工作方式。
顺便说一句,System.Object的GetType方法返回存储指定对象的“类型对象指针”成员中的地址。也就是说,GetType方法返回指向对象的类型对象指针。这样就可判断系统中任何对象的真实类型。

C# 类型、对象、线程栈和托管堆在运行时的关系的更多相关文章

  1. .NET中 类型,对象,线程栈,托管堆在运行时的关系

    .NET中 类型,对象,线程栈,托管堆在运行时的关系 The Relationship at Run Time between Types,Objects,A Thread's Stack,and T ...

  2. [读书心得] .NET中 类型,对象,线程栈,托管堆在运行时的关系

    .NET中 类型,对象,线程栈,托管堆 在运行时的关系 The Relationship at Run Time between Types,Objects,A Thread's Stack,and ...

  3. 类型,对象,线程栈,托管堆在运行时的关系,以及clr如何调用静态方法,实例方法,和虚方法(第二次修改)

    1.线程栈 window的一个进程加载clr.该进程可能含有多个线程,线程创建的时候会分配1MB的栈空间. 如图: void Method() { string name="zhangsan ...

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

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

  5. .Net 类型、对象、线程栈、托管堆运行时的相互关系

    JIT(just in time)编译器 接下来的会讲到方法的调用,这里先讲下JIT编译器.以CLR书中的代码为例(手打...).以Main方法为例: static void Main(){ Cons ...

  6. 【.Net基础一】 类型、对象、线程栈、托管堆运行时的相互关系

    目前在看CLR via C#,把总结的记下来,索性就把他写成一个系列吧. 1.[.Net基础一] 类型.对象.线程栈.托管堆运行时的相互关系 2.[.Net基础二]浅谈引用类型.值类型和装箱.拆箱 J ...

  7. 栈和托管堆/值类型和引用类型/强制类型转换/装箱和拆箱[C#]

    原文地址:http://www.cnblogs.com/xy8.cn/articles/1227228.html 一.栈和托管堆      通用类型系统(CTS)区分两种基本类型:值类型和引用类型.它 ...

  8. struct对象可能分配在托管堆上吗

    struct对象可能被分配在托管堆上吗? --会的. 比如当对struct装箱的时候,就会被分配在托管堆上. 比如,让一个struct实现一个接口. public interface IReport ...

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

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

随机推荐

  1. 「TJOI2015」线性代数

    题目链接 戳我 \(Describe\) 题目描述 为了提高智商,\(ZJY\)开始学习线性代数.她的小伙伴菠萝给她出了这样一个问题:给定一个\(n×n\)的矩阵\(B\)和一个\(1×n\)的矩阵\ ...

  2. react.js学习之路一

    今天新老大来了,我们要学习他使用的框架react.js,现在是两眼一抹黑,对于我这个前端菜鸟来说,是真正的重新开始,好了,不说那么多了,开始随便记录我的学习,之后再整理内容. (1)对于react来说 ...

  3. Ubuntu系统使用apache部署多个django项目(python4.3)

    /etc/apache2/sites-available/pyweb.conf <VirtualHost *:> ServerName 192.168.1.46 DocumentRoot ...

  4. 在FC中如何获取fcdot文件

    在FlexiCapture中一些客户在问如何获取.fcdot文件(在测试序列号下或者没有测试模板的情况下) 第一步: 1.查看License Manager查看是否找到序列号 首先我们在开始菜单里面打 ...

  5. C++_异常9-异常的注意事项

    一.先讨论异常被引发后,可能导致的问题 意外异常: 如果它是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配,否则为意外异常.在默认情况下,这将导致程序异常终止(虽然C++11摒弃了异常规 ...

  6. C# 关于Grid下动态删除行列的操作

    假设我们有以下布局 <Grid x:Name="Grid" ShowGridLines="True"> <Grid.RowDefinition ...

  7. 江西理工大学南昌校区排名赛 C: 单身狗的地图游戏

    题目描述 萌樱花是一只单身狗. 萌樱花今天决定去喜欢的学妹家玩,但他不记得路,于是他拿出有n个点的完全图,选取了k条不同的路. 完全图:n 个点的图中任意两个点之间都有一条边相连,所以有 n*(n-1 ...

  8. LightOJ - 1140 统计0的数位 数位DP

    注意以下几点: 搜索维度非约束条件的都要记录,否则大概率出错,比如_0 st参数传递和_0的互相影响要分辨清楚 num==-1就要返回0而不是1 #include<iostream> #i ...

  9. js关于原型,原型链的面试题

    之前面试的时候遇到过原型和原型链方面的题目,具体的已经忘了,只记得当时回答的稀里糊涂,今天查了一些资料,把自己所理解的写出来,加深记忆. 1,前提 在js中,对象都有__proto__属性,一般这个是 ...

  10. restFull接口实现web

    1. 模板引擎JSP的限制 在开始之前呢,我觉得我们有必要先去了解下 Spring Boot 2.0 官方文档中提到的如下内容: 模板引擎 除了REST Web服务之外,还可以使用Spring MVC ...