http://www.haogongju.net/art/227209

类型、对象、线程堆栈、托管堆栈之间运行时的相互关系(一)

 

首先来看一些基础知道再看看CLR是怎么工作的,看下图:

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

 

在一个最基本的方法中,应该包含一结“开场白(prologue)”代码,它们负责在方法开始做它的工作前对其进行初始化,还应该包含一结“收场白(epilogue)”代码,它们负责在方法完成工作后对其进行清理,以便返回给调用者。当M1开始执行时,它的“开场白”代码从线程的堆栈中为局部变量name分配内存,如下图:

然后,M1调用M2,将局部变量name作为一个实例来传递,这造成name局部变量中的地址被压入堆栈,如下图:

 

M2开始执行时,它的“开场白”代码从线程堆栈中为局部变量length和tally分配内存,如下图所示:

 

然后开始执行M2方法内部的代码。最终,M2会抵达它的return语句,这会造成CPU的指令指针被设置成堆栈中的返回地址,而且M2的堆栈帧会进行辗转开解(unwind),使之看起来类似于上面第二个图,之后,M1将继续执行在M2调用之后的代码,M1的堆栈帧将准确的反映M1需要的状态。

 

最终M1会返回到它的调用者,这同样是通过将CUP指令指针设计成返回地址来实现的,而且M1的堆栈帧会进行辗转开解,使之看起来类似于上面第一个图,之后,调用了M1的方法会继续执行在M1调用之后的代码,那个方法的堆栈帧将准确的反映它需要的状态。

 

 

类型、对象、线程堆栈、托管堆栈之间运行时的相互关系(二)

 

:摘自 Done博客  http://www.cnblogs.com/chenzehe/archive/2009/01/24/1380609.html

    纯属为了日后方便自己阅读

  看看CLR是怎么工作的,假定有以下两个类定义如下:

  internal class Employee

  {

          public Int32 GetYearsEmployed()

          {

                //...

          }

          public virtual String GenProgressReport()

          {

                //...

          }

          public static Employee Lookup(String name)

          {

                //...

          }

    }

    internal sealed class Manager : Employee

    {

          public override string GenProgressReport()

          {

              //...

          }

 }

 Windows进程已经启动,CLR已加载到其中,托管堆已经初始化,而且创建一个线程(连同它的1M的堆栈空间),该线程已经执行一些代码,现在马上要调用M3方法,如下图:

当JIT编译器将M3的IL代码转换成本地CPU指令时,会注意到M3内部引用的所有类型:Employee,Int32,Manager以及String(因为"Joe"),在这个时候,CLR要确保定义了这些类型的所有程序集都已经加载到AppDomain中,然后,利用程序集的元数据,CLR提取有关这些类型的信息,并创建一些数据结构来表示类型本身。下图展示了用于Employee和Manager类型对象我数据结构:

堆上的所有对象都包含两个额外的成员:类型对象指针和同步索引可以看出,上面Manager对象和Employee对象都有这两个成员。在定义一个类型时,可以在类型的内部定义静态数据字段,为这些字段提供支援的字节是在类型对象自身中分配的。在每个类型对象中,最后都包含一个方法表,在方法表中,类型中定义的每个方法都有一个对应的记录项,前面已经讨论过这个方法表。如上图,由于Employee类型定义了三个方法,GetYearsEmployed,GetProgressReport,Loolup,所以在Employee的方法表中有三个记录项。由于Manager类型只定义了一个方法,所以它的方法表中只有一个记录项。

当CLR确定方法需要的所有类型对象都已经创建,而且M3的代码经过编译之后,就允许线程开始执行M3的本地代码。M3的“开场白”代码执行时,必须从线程堆中分配内存,CLR会自动将所有的局部变量初始化为null或0,如下图:

然后M3执行它的代码来构造一个Manager对象,这造成在托管堆中创建Manager类型的一个实例,如下图:

和所有对象一样,Manager对象也有一个类型对象指针和同步块索引,该对象还包含容纳Manager类型定义的所有实例数据字段及其基类定义所需的所有实例字段所需的字节。任何时候在堆上新建一个对象,CLR都会自动初始化内部类型对象指针成员,让它引用与对象对应的类型对象(本例是Manager类型对象)。此外,CLR还会首先初始化同步块索引,并将对象的所有实例字段设为null或0,然后才调用类型的构造器,new操作符会返回Manager对象的内存地址,该地址保存在变量e中,在线程堆栈上。

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

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

M3的下一行代码调用Employee的非虚实例方法GetYearsEmployed,在调用一个非虚实例方法时,CLR会找到与发出调用的变量的类型对应的类型对象。在本例中,变量e被定义成一个Employee(假如Employee类型没有定义正在调用的方法,CLR会回溯类层次结构,在Object中查找该方法),然后,CLR在类型对象的方法表中找到引用了被调用方法的记录项,对方法进行JIT编译(如果需要的话),然后调用JIT编译过的代码,就本例来说,我们假定Employee的GetYearsEmployed方法返回5,如下图所示:

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

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

我们知道对象中包含一个指针,它指向对象的类型对象(类型对象中包含静态字段和方法表)。Employee和Manager类型对象都包含类型对象指针成员,这是因为类型对象本身实际上也是对象。CLR在创建类型对象时,必须初始化这些成员。CLR开始在一个进程中运行时,它会立即人System.Type类型在创建一个特殊的类型对象,Employee和Manager类型对象是该类型的“实例”,因此,它们的类型对象指针成员会初始化成对System.Type类型对象的引用,当然,System.Type类型对象本身也是一个对象,所以内部也包含一个类型对象指针成员,它指向它本身,因为System.Type类型对象本身是一个类型对象的“实例”

,如下图:

posted on 2012-05-03 15:59  higirle  阅读(487)  评论(0编辑  收藏  举报