对象创建始末
内存分配:
线程堆栈:用于分配值类型实例,堆栈由os管理不受GC控制
GC堆:分配小对象实例。如果引用类型对象实例大小小于85000字节实例将被分配在GC堆
LOH(LargeObjectHeap)堆:大于85000的引用类型对象
实例创建的IL指令
newobj:创建引用类型对象
ldstr:创建string类型对象
newarr:分配新数组对象
box:值类型转换为引用类型对象时,将值类型字段拷贝到托管堆上发生的内存分配
堆栈的分配机制:
对于值类型,一般创建在线程堆栈上。但并非所有值都创建在线程堆栈,例如
作为类的字段时,值类型也作为实例成员被创建在托管堆上;装箱发生时,值类型也会拷贝在托管堆上
对于分配在
堆栈上的局部变量来说,
os维护一个堆栈指针指向下一个自由空间地址,并且其地址
由高位到地位向下填充
栈上的内存分配效率较高,但内存容量不大,同时变量的生存周期随着方法结束而消亡
托管堆的内存分配机制:
引用类型的实例
分配于托管堆上,而
线程栈是对象生命周期开始的地方。
对32位cpu来说,
app完成进程初始化后,
CLR将在进程可用地址空间上分配一块保留的地址空间,它是进程中可用地址空间上的一块内存区域,但并不对应于任何物理内存,这块地址空间即是
托管堆;
托管堆划分:
垃圾回收堆(GC Heap):用于存储对象实例,受GC管理
加载堆
(Loader Heap):
分为High-Frequency Heap,Low-Frequency Heap和Stub Heap;
加载堆最重要信息是
元数据相关信息,即Type对象,每个Type在Loader Heap上体现一个方法表,而方法表中记录存储的元数据信息,例如基类型/静态字段/实现的接口/所有的方法等,它不受GC控制,生命周期从创建到AppDomain卸载
几个基本概念
TypeHandle:类型句柄,指向对应实例的方法表,每个对象创建都包含该附加成员并占用4字节。每个类型对应一个方法表,方法表创建于编译时,主要包含类型的特征信息/实现的接口数目/方法表的slot数目等
SyncBlockIndex:用于线程同步,每个对象创建也附加该成员,它指向成为Synchronization Block的内存块,用于管理对象同步,占用4字节
NextObjPtr:托管堆维护的一个指针,用于标识下一个新建对象分配时在托管堆中所处的位置。CLR初始化时NextObjPtr位于托管堆的基地址
注意,
栈的分配向低地址扩展,而堆的分配向高地址扩展;另外,
实例字段的存储有顺序的,由上到下一次排列,父类在前子类后。
值类型中的引用类型字段,堆栈上保存该成员的引用,实际的引用类型仍然保存在GC堆上
引用类型嵌套值类型,则该值类型字段将作为引用类型实例的一部分保存在GC堆上
(值类型,只要记住它
总是分配在声明它的地方)
方法保存在LoaderHeap的MethodTable中,调用时过程如下:
MethodTable中包含了类型元数据信息,
类
加载时会在LoaderHeap上创建这些信息,一个类型在内存中
对应一份MethodTable,其中
包含了所有的方法/静态字段和实现的接口信息等。
对象实例
的TypeHandle在实例创建时,将指向MethodTable开始位置偏移处(默认偏移12Byte),通过对象实例调用某个方法时,CLR根据TypeHandle可以找到对应MethodTable,进而定位到具体方法,再通过JIT Compiler将IL指令编译为本地CPU指令,该指令将保存在一个动态内存中,然后再该内存地址上执行该方法,同时该CPU指令被保存起来用于下一次执行。
MethodTable中,包含一个Method Slot Table,成为方法槽表,该表是一个基于方法实现的线性链表,并按照下顺序排列:继承的虚方法,引入的虚方法,实例方法和静态方法。方法表创建时,将按照继承层次向上搜索父类,知道System.Object类型
静态字段的内存分配释放:
静态字段保存在方法表中,位于方法表的槽数组后,生命周期为从创建到AppDomain卸载。因此一个类型无论创建多少对象,静态字段在内存中也只有一份。