CLR加载程序生成进程,一个进程中可以存在多个线程,当创建一个线程时,会分配1Mb的空间,也就是线程的栈空间,对应jvm的虚拟机堆栈,是线程执行过程中用到的工作内存。这片内存用于方法传递实参,并存储方法内部定义的局部变量,栈是从高位地址向低位地址构建。栈是由操作系统直接管理,不受GC控制,当执行的方法退出后,栈中的存储会自动释放。栈的效率很高,但存储空间有限。
对象的数据存储在堆上,堆上的数据是所有线程共享的,堆根据类型又分为:GC堆、加载堆(Loader Heap)、大对象(LOH)堆。当对象实例大小小于85000字节时,该对象会被分配到GC堆上,GC回收主要是回收这一片区域,回收过后会进行压缩。当对象实例大小大于85000个字节时,会被分配到LOH堆,只有GC进行完全回收时这片区域的数据才会被回收,且回收后不会压缩。
加载堆(Loader Heap)用来存储类型的元数据,也就是类的Type信息,反射的时候就是用到这里面的信息。每个类在加载堆中对应一个方法表(Method Table),里面记录该类的元数据信息,比如基类型、静态字段、继承的接口、所有的方法等。加载堆不受GC控制,不会被回收,其生命周期是从创建(第一次使用)直到AppDomain卸载。
当我们在程序中new一个对象时,会根据这个对象的大小(字段数据等)在GC堆中生成这个对象的内存空间,其中字段数据直接在这个对象空间中,同时这个对象会包含两个引用:TypeHandle和SyncBlockIndex。其中TypeHandle指向加载堆(Loader Heap)中的方法表(Method Table),SyncBlockIndex指针指向Synchronization Block的内存块,用于在多线程环境下对实例对象的同步操作。当这些都准备好以后,会将GC堆中的内存地址返回给栈中该变量,这里也是整个new的过程。
这里有必要对加载堆(Loader Heap)做下补充,其实在类第一次使用(不一定是new)的时候,类的元数据信息就会被放到加载堆(Loader Heap)中。当真正new一个对象的时候,其实加载堆(Loader Heap)中已经有该类的元数据信息,如果没有,也会先创建。然后再将这个对象的TypeHandle和加载堆中的该类型的MethodTable对应起来。
以上所有描述,可参考下图: