• 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类空间。

      

  • 相关阅读:
    Pig与Hive的区别
    Hadoop MapReduceV2(Yarn) 框架简介
    Spark技术内幕:Client,Master和Worker 通信源码解析
    Spark技术内幕:Stage划分及提交源码分析
    无责任比较thrift vs protocol buffers
    理解hadoop的Map-Reduce数据流(data flow)
    hadoop-2.5安装与配置
    linux下查看本地程序占用的端口
    MFC安装与部署(程序打包)
    关系数据库设计中数据字典设计例子
  • 原文地址:https://www.cnblogs.com/cowman/p/3034748.html
Copyright © 2020-2023  润新知