CLR via C#学习之线程栈,托管堆,值类型和引用类型 cow

  最近在系统的读CLR via C#这本书,发现写得很好。但是抽象的概念比较多,有些地方理解起来表费劲耗时,所以在这里记录下自己觉得重要的地方。

  本文要阐述的主要内容:在运行时,值类型和引用型是如何在线程栈和托管堆中工作的。

  

     线程栈的基本概念及资源分配:

  线程栈的创建:在windows进程加载完CLR,创建一个线程后,大小为1MB的线程栈被创建。

  线程栈的作用存储形参和局部变量。如图所示name和m2方法的形参将会被存放在线程栈中。

  疑问为什么全局变量不存放在线程栈中?全局变量应该是在类里面,类作为引用类型自然是存放在托管堆中。

  

  图4-3 执行String name="Joe"后,CLR会在线程栈中name分配空间。

  

  图4-4

  1.执行M2(name)之前,CLR将name的拷贝s作为实参,存放在线程栈中。

  2.执行M2(name)之前,CLR将函数的执行返回地址(return address)存放在线程栈中,以便M2方法执行完成好,回到M1中继续执行。

  

  

  图4-5

    1.M2方法内的局部变量存放在线程栈中。

    2.M2方法执行完成后,清空M2中局部变量,同时取出return address,回到M1方法中继续执行。此时线程栈中的资源只剩下name了,如图4-3所示。

  

  

  托管堆中的资源分配

  我们有两个类定义如下:

internal class Employee { 
   public         Int32     GetYearsEmployed()   { ... } 
   public virtual String    GetProgressReport()  { ... } 
   public static  Employee  Lookup(String name)  { ... } 

 
internal sealed class Manager : Employee { 
   public override String   GetProgressReport()  { ... } 
}

  图4-6 windows进程启动,CLR加载完成,托管堆完成初始化,线程被创建,且执行了一部分代码,接下来准备执行M3方法。

  

  

  

  图4-8

  1.在实行M3方法之前,JIT编译器将IL代码编译成本地CPU指令。

  此外JIT编译器还做了的两件事情:

  (1)通过查询元数据信息,加载M3方法中引用的所有类型的程序集(如果程序集已经加载,就跳过)。

  (2)通过查询元数据信息,在托管堆中创建引用的类。

  2.执行M3中申明变量e和year代码时,在线程栈中分配两个变量。

  

  图4-9

  1.执行e=new Manager()时,根据元数据找到对应的模板(Manager Type Object),然后创建实例对象Manager object,并且修改线程栈中变量e的值,让它指向Manager object对象。

  2.对象指针指向相应的类型对应指针。

  3.由图中可以看出对象在托管堆中包括三部分:对象指针,同步索引块,对象字段。

  疑问1:对象的方法存放在哪里?由图可以看出方法存放在类中。

  疑问2: 实例的方法存放在类对象中,多线程访问实例的方法的过程是什么样子的?

  答:假设方法已经编译成IL指令集合(包括指令1,指令2,指令3)。线程1进入方法访问完指令1,2后,保存线程现场。线程2开始访问指令1,2,此时CPU的权限又移交给线程1,线程1恢复线程现场,接着执行指令3完成后,线程1回到线程池或者关闭。CPU把控制权交给线程2,继续执行指令3,完成后线程3回到线程池。

  疑问2的解答是我个人理解,如果不正确,欢迎指教。

  图4-10

  同上一步类似。JIT编辑器讲LookUp方法编译成IL指令,并执行指令,在托管堆中创建Joe对象。

  图4-11 JIT编译器将GetYearsEmployed编译成CPU指令,并执行后,将值存放在线程栈的Year变量中。

  图4-12

  (1)执行e.GenProgressReport方法时,难点在于是执行基类还是子类中的方法。

  (2)对于虚方法的执行,需要检查变量类型(Employee)和托管堆类型(Manager)是否一致,如果不一致,需要做额外的处理。

  (3)不一致的话,需要通过对象指针定位到类型对象,然后找到对应的方法。因为Manage中改方法前面是Override,所以找到的是Manager类中的GenProgressReport方法。

  疑问:如果Manger中的GenProgressReport方法前面是用new修饰的,托管堆的图该怎么画?e.GenProgressReport方法应该如何执行?

  答:1)类对象(Manager Type Object)应该生成两个同名方法,并且在元数据中做好标记(该方法是否为子类中的方法)。

    2)执行e.GenProgressReport方法时,因为类对象中有两个方法,根据引用变量e的类型为基类,应该调用基类中继承过来的同名方法。

  疑问2:类A继承类B,里面的方法,相同的方法在内存中是一块还是两块?

  

  答:这个问题不是本文重点,将在下一篇博客中解决。

  图4-13 Employee和Manager的类型对象是System.Type类型的实例。所以在CLR加载完成,托管堆初始化后,立马在托管对中创建System.Type类空间。

  

原文地址:https://www.cnblogs.com/cowman/p/3034748.html