欢迎阅读本系列其他文章:

【读书笔记】.NET本质论第一章 The CLR as a Better COM
【读书笔记】.NET本质论第二章-Components(Part One)
【读书笔记】.NET本质论第二章-Components(Part Two,public key)
【读书笔记】.NET本质论第二章-Components(Part Three,CLR Loader)
【读书笔记】.NET本质论第二章-Components(Part Four,Assembly Resolver)
【读书笔记】.NET本质论第三章-Type Basics(Part 1)
【读书笔记】.NET本质论第三章-Type Basics(Part 2)
【读书笔记】.NET本质论第三章-Type Basics(Part 3)
【读书笔记】.NET本质论第四章-Programming with Type(Part One)

本章的第一篇简单的说了一下值类型和引用类型,以及编写代码时对对象日后在内存中布局的影响。本篇会重点关注对象加载到内存后是个什么样子,是个什么样的结构,而这些结构在运行时又起到什么作用。

下图是一个对象在内存中的结构:

所有引用类型,分配在托管堆上时,都会分配额外的两个字段,同步块索引与方法表指针,在32位中,这两个字段各占4个字节。紧跟在方法表后面的就是该对象的实例字段。

关于同步块索引这个字段,我已经用了三篇文章来介绍了,在这里就不再重复了:

揭示同步块索引(上):从lock开始

揭示同步块索引(中):如何获得对象的HashCode

揭示同步块索引(下):总结

那本篇文章就主要讨论这个方法表指针以及相关的东西。

读过《.NET本质论》的读者也许会发现,原书说的在MethodTable Pointer的地方是htype,也就是所谓的“类型句柄”,那类型句柄和方法表指针又是什么关系呢?这个在文章稍后再做探讨。我们在这里就先只关注方法表指针和方法表。

其实方法表(MethodTable)这个名字并不能表达出它本身的含义,可以说还让人产生误解。实际上方法表根本就不是一个“表”,从主体的结构上也没有看到任何表的意思(实际上所谓的方法的列表时附加在MethodTable主体结构之后的,要访问方法表,只需要用this+offset,这样做纯粹是为了性能的考虑,因为一个类型的方法个数不定,常规做法肯定是在另外一个地方动态的开辟一块内存保存这个方法的列表,然后在MethodTable内包含一个指向这个列表的指针,因为这个方法列表在方法调用的时候需要频繁使用,性能非常重要,如果使用指针的方法,就多了一层,是间接的访问,而现在这种设计只需要加一个偏移就够了),关于方法表结构的更详细描述在后面的文章中,下图是一个简图:

方法表实际上包含的是对一个类型的详细描述,比如该类型的父类,接口,当然还包括该类型的方法。实际上和方法表共存的还有一个东西:EEClass。EEClass和方法表所要表示的东西一样,都是根据类型的元数据构建而来。那为什么要分两个东西呢?对于一个类型的元数据来说,有一部分是经常用到的,比如Virtual Disaptch,这些数据称之为“Hot Data”,还有一部分却不经常使用,这部分就放在EEClass中,而方法表中有指向EEClass的指针。但是方法表和EEClass并不是一一对应的关系,比如一个泛型类型,如果泛型的参数是引用类型的话(比如MyClass<string>和MyClass<Object>),它们的EEClass是共享的,而方法表却不同。

方法表在多态、类型检查、JIT中都起着非常重要的作用,是.NET的核心之一。当第一次执行某方法时,CLR会检查该方法内要使用的所有类型,如果这些类型没有加载则会加载这些类型,加载类型的时候还要看看该类型的程序集是否加载了,如果没加载则首先加载程序集,然后读取程序集中的元数据,先构建EEClass,然后构建方法表。在AppDomain中这样的俩个堆:High-Frequency-Heap和Low-Frequency-Heap,方法表就会加载到High-Frequency-Heap,EEClass加载到Low-Frequency-Heap。注意,这两个Heap都不属GC的管辖,所以一个类型加载后,不会因为不再使用而被清理,只会等到AppDomain卸载后才会清理。对于普通的WinForm程序,默认情况下只有等到程序关闭的时候AppDomain才会Unload,对于ASP.NET或寄宿在其他Host中的程序(比如Sql Server 2005开始提供对CLR的支持),这个就要看Host到底是如何调度的。

上面光用文字说了这么多,下面就用Visual Studio+SOS建立一些感性的认识,为后面根据Rotor的代码分析方法表建立基础。

Demo1:

   1: namespace Yuyijq.Study.MethodTable
   2: {
   3:     interface IFoo
   4:     {
   5:         void Run();
   6:     }
   7:     class Foo : IFoo
   8:     {
   9:         private int _instanceField = 5;
  10:         private static int _staticField = 6;
  11:  
  12:         #region IFoo Members
  13:  
  14:         public void Run()
  15:         {
  16:             
  17:         }
  18:  
  19:         #endregion
  20:     }
  21:     class Program
  22:     {
  23:         static void Main(string[] args)
  24:         {
  25:             IFoo f = new Foo();
  26:             f.Run();
  27:  
  28:             Console.ReadLine();
  29:         }
  30:     }
  31: }

看看这里的Foo类,实现了IFoo接口,有一个实例字段,一个静态字段,实现了IFoo的Run方法。我们在Console.ReadLine()处设置断点,通过下面这两个命令我们找到了Foo类型的方法表地址(00463120):

   1: !dso
   2: PDB symbol for mscorwks.dll not loaded
   3: OS Thread Id: 0x1514 (5396)
   4: ESP/REG  Object   Name
   5: 002eefa8 01a92de4 System.Object[]    (System.String[])
   6: 002ef32c 01a92df4 Yuyijq.Study.MethodTable.Foo
   7: ....
   8:  
   9: !do 01a92df4
  10: Name: Yuyijq.Study.MethodTable.Foo
  11: MethodTable: 00463120
  12: .....

使用dumpmt –md 00463120,我们看看MethodTable可以给我们哪些信息:

   1: !dumpmt -md 00463120
   2: EEClass: 00461360
   3: Module: 00462c5c
   4: Name: Yuyijq.Study.MethodTable.Foo
   5: mdToken: 02000003  (E:\Study\ConsoleApplication10\ConsoleApplication10\bin\Debug\Yuyijq.Study.MethodTable.exe)
   6: BaseSize: 0xc
   7: ComponentSize: 0x0
   8: Number of IFaces in IFaceMap: 1
   9: Slots in VTable: 7
  10: --------------------------------------
  11: MethodDesc Table
  12:    Entry MethodDesc      JIT Name
  13: 66046a90   65ec1248   PreJIT System.Object.ToString()
  14: 66046ab0   65ec1250   PreJIT System.Object.Equals(System.Object)
  15: 66046b20   65ec1280   PreJIT System.Object.GetHashCode()
  16: 660b74c0   65ec12a4   PreJIT System.Object.Finalize()
  17: 0046c038   004630fc      JIT Yuyijq.Study.MethodTable.Foo.Run()
  18: 0046c040   00463108      JIT Yuyijq.Study.MethodTable.Foo..ctor()
  19: 0046c048   00463114      JIT Yuyijq.Study.MethodTable.Foo..cctor()

在这里可以知道EEClass的地址是00461360,该类型实现了几个接口:Number of IFaces in IFaceMap:1。方法表的方法数目Slots in VTable: 7,以及下面跟着的方法表。还可以知道该类型的实例将占用多少字节的内存 BaseSize: 0xC(12个字节,一个int类型的实例字段和额外开辟的8个字节)。而我们非常关心的就是下面的MethodDesc Table(这个在后面的文章再深入讨论)。

除此之外,我们还可以使用dumpclass 00461360命令查看EEClass的细节:

   1: !dumpclass 00461360
   2: Class Name: Yuyijq.Study.MethodTable.Foo
   3: mdToken: 02000003 (E:\Study\ConsoleApplication10\ConsoleApplication10\bin\Debug\Yuyijq.Study.MethodTable.exe)
   4: Parent Class: 65e83a88
   5: Module: 00462c5c
   6: Method Table: 00463120
   7: Vtable Slots: 5
   8: Total Method Slots: 7
   9: Class Attributes: 100000  
  10: NumInstanceFields: 1
  11: NumStaticFields: 1
  12:       MT    Field   Offset                 Type VT     Attr    Value Name
  13: 660eab0c  4000001        4         System.Int32  1 instance           _instanceField
  14: 660eab0c  4000002       1c         System.Int32  1   static        6 _staticField

哦,在这里记录了更多的一些信息,实例字段个数,还有一个列表,注意在这个列表里,对于静态字段还列出了值,而实例字段没有,这是为什么呢?聪明的你不难想到吧。嗯,这里还记录了Foo是从哪个基类派生的:Parent Class: 65e83a88,我们来看看是什么类型:

   1: !dumpclass 65e83a88
   2: Class Name: System.Object
   3: mdToken: 02000002 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
   4: Parent Class: 00000000
   5: Module: 65e81000
   6: Method Table: 660e84dc
   7: Vtable Slots: 4
   8: Total Method Slots: a
   9: Class Attributes: 102001  
  10: NumInstanceFields: 0
  11: NumStaticFields: 0

原来是System.Object,而System.Object的Parent Class是00000000。

后记

通过前面的文字描述,和后面的调试,我想你应该对MethodTable和EEClass有一点点的了解了,而在下一篇文章中,我们会一边欣赏Rotor的源代码,一遍来讨论方法表里的各个字段有什么作用。

突然一看,文章标题虽然是【.NET本质论读书笔记】,但却已经偏离了书本的内容,请大家不要见怪,我只想以.NET本质论的结构和描述顺序作为主线来梳理CLR的一些东西。文章内容只会跟着书的主线,但不拘泥书中讨论的内容。

祝编程愉快

【读书笔记】.NET本质论第四章-Programming with Type(Part Two)相关推荐

  1. 【百面机器学习之算法工程师读书笔记】——第十四章:人工智能的热门应用-游戏AI

    目录 游戏AI发展史 从AlphaGo到AlphaGo Zero 德州扑克中的"唬人"AI AI电子竞技 星际争霸:走向通用AI 为什么AI需要游戏? 游戏AI发展史 (1)195 ...

  2. B端产品实战课读书笔记05:第四章需求调研

    目录 一.调研准备 1.提炼原始信息 2.设定调研目标 2.1价值共识 2.2需求共识 2.3理解共识 3.规范调研准则 3.1全程参与 3.2相互尊重 3.3聚焦问题 3.4开放包容 二.快速掌握业 ...

  3. [读书笔记]Effective Java 第四章

    使类和成员的可访问性最小化 规则很简单:尽可能地使每个类或者成员不被外界访问.实例域(非final)决不能是公有的.当需要暴露出不可变的实例时通常会把这个实例做成不可变或者是把这个实例变成私有,同时提 ...

  4. Linux_UNIX编程手册-读书笔记-第五十四章(POSIX共享内存)

    54.1 概述 POSIX共享内存能够让无关程序共享一个映射区域而无需创建一个相应的映射文件. linux使用挂载与/dev/shm目录下的专用tmpfs文件系统,系统上POSIX共享内存区域占据的内 ...

  5. 《深入理解计算机系统》读书笔记-016(第 12 章 并发编程)

    <深入理解计算机系统>读书笔记-016(第 12 章 并发编程) 太惨了,这章真心不大看得懂啊--等把前面的补上之后把读书笔记重新整理一下吧.这样看了跟没看也没啥区别了. 在线程中,不同于 ...

  6. 《资本论》读书笔记(2)第二卷第一章:资本形态变化及其循环

    <资本论>读书笔记(2)第二卷第一章:资本形态变化及其循环 +BIT祝威+悄悄在此留下版了个权的信息说: 货币资本的循环 第一阶段:资本家用手里的钱买来设备.原材料,雇来一批工人,或者说, ...

  7. 李弘毅机器学习笔记:第十四章—Why deep?

    李弘毅机器学习笔记:第十四章-Why deep? 问题1:越深越好? 问题2:矮胖结构 v.s. 高瘦结构 引入模块化 深度学习 使用语音识别举例 语音辨识: 传统的实现方法:HMM-GMM 深度学习 ...

  8. mysql函桌为之一的_MYSQL必知必会读书笔记第十和十一章之使用函数处

    mysql简介 MySQL是一种开放源代码的关系型数据库管理系统(RDBMS),MySQL数据库系统使用最常用的数据库管理语言--结构化查询语言(SQL)进行数据库管理. 拼接字段 存储在数据库表中的 ...

  9. 《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第二章 渲染管线 The Graphics Rendering Pipeline

    写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了.不对之处甚多,以后理解深刻了,英语好了再回来修改.相信花在本书上的时间和精力是值得的. -- ...

最新文章

  1. 数据结构与算法(6-2)二叉树的存储结构(顺序存储、链式存储)
  2. Python学习教程(Python学习视频_Python学些路线):Day05 总结和练习
  3. iosttableViewCell右侧的箭头,圆形等
  4. docker和docker-compose 端口映射
  5. [网络安全提高篇] 一〇六.SQL注入之手工注入和SQLMAP入门案例详解
  6. Oracle性能调优方法
  7. raspberry pi_如何进行Raspberry Pi聚会
  8. oracle实例名,数据库名,服务名等概念差别与联系
  9. C语言中的`sprintf`和`sscanf`两个函数介绍
  10. Dual Thrust(期货)
  11. 消息队列原理及activeMQ基本知识点
  12. Python简单换脸程序
  13. 简洁的圆形时钟数字时钟+指针时钟(1+X Web前端开发初级 例题)
  14. JavaWeb-16 (E家园项目案例1)
  15. 阿里云DNS专家,手把手教你定位域名解析不生效
  16. 岛屿最大面积 leetcode Java_LeetCode:岛屿的最大面积
  17. HarmonyOS应用开发 — HelloWorld应用开发E2E体验
  18. 数据源SqlDataSource,DetailView,ObjectDataSource控件的配置使用
  19. 矩阵合同,相似与等价 以及初等变换矩阵
  20. 世界各国英文简写代码

热门文章

  1. Java高并发入门-线程初步(二)
  2. oracle 表改表空间,Oracle批量修改用户表table的表空间
  3. LinuxControlGroup(Cgroup)简介
  4. seata分布式事务协调管理器是如何实现的
  5. ElasticSearch常用的分词器
  6. 日志服务与SIEM(如Splunk)集成方案实战 1
  7. 云计算---openstack基础构架以及服务方式详解
  8. Jquery需要掌握的技巧
  9. 输入输出流——字符流部分
  10. 使用Event Message 对 Package 进行Troubleshoot