
每个线程对应一个stack线程创建的时候CLR为其创建这个stackstack主要作用是记录函数的执行情况。值类型变量函数的参数、局部变量 等非成员变量都分配在stack中引用类型的对象分配在heap中在stack中保存heap对象的引用指针。GC只负责heap对象的释 放heap内存空间管理Heap内存分配除去pinned object等影响heap中的内存分配很简单一个指针记录heap中分配的起始地址根据对象大小连续的分配内存Stack结构每个函数调用时逻辑上在thread stack中会产生一个帧stack frame函数返回时对应的stack frame被释放掉用个简单的函数查看执行时CLR对栈的处理情况static void Main(string[] args){int r Sum(2, 3, 4, 5, 6);}private static int Sum(int a, int b, int c, int d, int e){return a b c d e;}JIT编译后主要汇编代码如下其他的情况下汇编代码可能有所差别但用这个简单函数大致看下栈的管理已经足够;函数Mainpush 4 ;第3个参数到最后一个参数压栈push 5push 6mov edx,3 ;第1、第2个参数分别放入ecx、edx寄存器mov ecx,2call dword ptr ds:[00AD96B8h] ;调用函数Sum执行call的时候返回地址即下面这条mov语句的地址自动压栈 了mov dword ptr [ebp-0Ch],eax ;将函数返回值设置到局部变量r中函数调用结束返回值在eax寄存器中;函数Sumpush ebp ;保存原始ebp寄存器mov ebp,esp ;将当前栈指针保存在ebp中后面使用ebp对参数和局部变量寻址sub esp,8 ;分配两个局部变量mov dword ptr [ebp-4],ecx ;第1个参数放入局部变量mov dword ptr [ebp-8],edx ;第2个参数放入局部变量...... ;CLR的检查代码mov eax,dword ptr [ebp-4] ;a b c d eadd eax,dword ptr [ebp-8] ;第1个参数第2个参数23add eax,dword ptr [ebp10h] ;第3个参数4add eax,dword ptr [ebp0Ch] ;第4个参数5add eax,dword ptr [ebp8] ;第5个参数6mov esp,ebp ;恢复栈指针局部变量被释放了pop ebp ;恢复原始的ebp寄存器值ret 0Ch ;函数返回. 1: 返回地址自动出 栈; 2: esp减去0Ch12个字节即从栈中清除调用参 数; 3: 返回值在eax寄存器中执行时刻的stack状态如下栈基地址为高端地址栈顶为低端地址Stack状态变化过程a). 调用者将第3、第4、第5个参数压栈第1、第2个参数分别放入ecx、edx寄存器b). call指令调用函数Sum并自动将函数返回地址压栈代码跳转到函数Sum开始执行c). 函数Sum先将寄存器ebp压栈保存并将esp放入ebp用于后面对参数和局部变量寻址d). 定义局部变量以及省略掉的是额外代码跟Sum函数业务无关e). 执行加法操作结果保存在eax寄存器中f). 恢复esp寄存器这样函数Sum中所有的局部变量以及其他压栈操作全部释放出来g). 原始ebp的值出栈恢复ebp这样栈完全恢复到进入Sum函数调用时的状态h). ret指令执行函数返回返回值在eax寄存器中返回地址为call指令压栈的地址返回地址自动出栈。0Ch指示处理器在函数返回时释放栈中12个字 节即由被调用者清除压栈的参数。函数返回之后本次Sum调用的栈分配全部释放这种调用约定类似__fastcall结合引用类型变量、值类型的ref参数下面代码简化的stack状态如下代码public static void Run(int i){int j 9;MyClass1 c new MyClass1();c.x 8;int result Sum(i, 5, ref j, c);}public static int Sum(int a, int b, ref int c, MyClass1 obj){int r a b c obj.x;return r;}public class MyClass1{public int x;}Stack状态任何时候引用类型都分配在heap中在stack中只是保存对象的引用地址。Run函数执行完毕之后heap中的MyClass1对象c成为可回收的垃圾对象在GC时进行回收2. Mark-Compact 标记压缩算法简单把.NET的GC算法看作Mark-Compact算法阶段1: Mark-Sweep 标记清除阶段先假设heap中所有对象都可以回收然后找出不能回收的对象给这些对象打上标记最后heap中没有打标记的对象都是可以被回收的阶段2: Compact 压缩阶段对象回收之后heap内存空间变得不连续在heap中移动这些对象使他们重新从heap基地址开始连续排列类似于磁盘空间的碎片整理Heap内存经过回收、压缩之后可以继续采用前面的heap内存分配方法即仅用一个指针记录heap分配的起始地址就可以主要处理步骤将线程挂起确定roots创建reachable objects graph对象回收heap压缩指针修复可以这样理解rootsheap中对象的引用关系错综复杂交叉引用、循环引用形成复杂的graphroots是CLR在heap之外可以找到的 各种入口点。GC搜索roots的地方包括全局对象、静态变量、局部对象、函数调用参数、当前CPU寄存器中的对象指针还有finalization queue等。主要可以归为2种类型已经初始化了的静态变量、线程仍在使用的对象stackCPU registerReachable objects指根据对象引用关系从roots出发可以到达的对象。例如当前执行函数的局部变量对象A是一个root object他的成员变量引用了对象B则B是一个reachable object。从roots出发可以创建reachable objects graph剩余对象即为unreachable可以被回收指针修复是因为compact过程移动了heap对象对象地址发生变化需要修复所有引用指针包括stack、CPU register中的指针以及heap中其他对象的引用指针Debug和release执行模式之间稍有区别release模式下后续代码没有引用的对象是unreachable的而debug模式下需要等到 当前函数执行完毕这些对象才会成为unreachable目的是为了调试时跟踪局部对象的内容传给了COM的托管对象也会成为root并且具有一个引用计数器以兼容COM的内存管理机制引用计数器为0时这些对象才可能成为被回收对象Pinned objects指分配之后不能移动位置的对象例如传递给非托管代码的对象或者使用了fixed关键字GC在指针修复时无法修改非托管代码中的引用 指针因此将这些对象移动将发生异常。pinned objects会导致heap出现碎片但大部分情况来说传给非托管代码的对象应当在GC时能够被回收掉3. Generational 分代算法程序可能使用几百M、几G的内存对这样的内存区域进行GC操作成本很高分代算法具备一定统计学基础对GC的性能改善效果比较明显将对象按照生命周期分成新的、老的根据统计分布规律所反映的结果可以对新、老区域采用不同的回收策略和算法加强对新区域的回收处理力度争取在较短 时间间隔、较小的内存区域内以较低成本将执行路径上大量新近抛弃不再使用的局部对象及时回收掉分代算法的假设前提条件a). 大量新创建的对象生命周期都比较短而较老的对象生命周期会更长b). 对部分内存进行回收比基于全部内存的回收操作要快c). 新创建的对象之间关联程度通常较强。heap分配的对象是连续的关联度较强有利于提高CPU cache的命中率.NET将heap分成3个代龄区域: Gen 0、Gen 1、Gen 2Heap分为3个代龄区域相应的GC有3种方式: # Gen 0 collections, # Gen 1 collections, # Gen 2 collections。如果Gen 0 heap内存达到阀值则触发0代GC0代GC后Gen 0中幸存的对象进入Gen 1。如果Gen 1的内存达到阀值则进行1代GC1代GC将Gen 0 heap和Gen 1 heap一起进行回收幸存的对象进入Gen 2。2代GC将Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收Gen 0和Gen 1比较小这两个代龄加起来总是保持在16M左右Gen 2的大小由应用程序确定可能达到几G因此0代和1代GC的成本非常低2代GC称为full GC通常成本很高。粗略的计算0代和1代GC应当能在几毫秒到几十毫秒之间完成Gen 2 heap比较大时full GC可能需要花费几秒时间。大致上来讲.NET应用运行期间2代、1代和0代GC的频率应当大致为1:10:100图为一个ASP.NET程序运行的Performance MoniterGen 0 heap size(红色)平均6MGen 1(蓝色)平均5MGen 2(黄色)达到620MGen 0Gen 1平均13.2M最大19.8M直观上来看程序的运行由一系列函数调用组成函数运行期间会创建很多局部对象函数结束之后也就产生大量待回收的对象。采用分代算法加强较新代龄的垃圾 回收力度通常能够极大的提高垃圾回收效率否则就是极特殊的程序或者是不合理的对象关联设计。例如ASP.NET程序应当确保绝大部分用于HTTP 请求处理的对象在0代和1代垃圾回收中被释放掉为heap记录几个指针可以确定代龄区域范围创建reachable objects graph时根据对象的地址可以确定对象位于哪个代龄区域0代GC在创建graph时如果遇到1代、2代heap对象可以直接越过不用继续遍历下去 较老代龄的对象如果引用了较新代龄的对象可以通过Win32 API GetWriteWatch订阅内存更新通知记录在card table中辅助较低代龄的GC正确构造graph4. LOH