1、从托管堆中分配资源。
当应用程序的进程完成初始化后,CLR会在内存中保留一块连续的地址空间作为托管堆,用来为新建的引用对象分配内存空间。在托管堆中维护着一个NextObjPtr指针,指向下一个新建对象分配时在托管堆中所处的位置,当对象通过newobj指令被创建时,CLR检查托管堆中的空间是否能满足分配新对象所需的字节数,如果能满足则对象会被分配到NextObjPrt指针所指示的位置,并且NextObjPrt指针会调整到下一个新建对象在托管堆中的地址。但是,当托管堆没有足够的地址空间来分配该对象时,则需要执行垃圾收集,回收内存空间。同时newobj指令会为对象分配两个开销字段,类型对象指针和同步块索引。
2、 垃圾收集是如何进行的?
首先,每个应用程序都有一组根,存放着指向引用类型对象的指针,系统会把静态变量、方法参数、局部变量和CPU寄存器等对象视为根。
垃圾收集会经历"标记"和"压缩"两个阶段。首先,垃圾收集器会遍历所有的根,如果发现根引用了对象,则在对象的同步块索引中设置一个位,表明是一个可达对象。完成"标记"阶段后,堆中就包含了一组已标记和未标记的对象,其中已标记的为可达对象,未标记的为不可达对象。不可达对象会被认为是垃圾,将会在"压缩"阶段进行回收。在托管堆中的内存被压缩之后,托管堆上的NextObjPtr指针将被设为指向最后一个非垃圾对象之后。
因为垃圾收集会给应用程序带来性能开销,所以垃圾收集使用了代的机制来提高垃圾收集的性能。总共分为0代、1代和2代。第0代对象是哪些最近分配的对象,它们还没有被垃圾收集器的算法检查过。在垃圾收集中存活下来的对象将被提升到另一代。具体过程是,如果分配新对象导致第0代对象超过了预算容量(如256KB),则会触发第0代对象的垃圾收集,如果在第0代的垃圾收集中存活下来的对象会被提升到第1代对象。如果第1代对象超过了预算容量(如2MB),同样也会触发第1代对象的垃圾收集,如果存活下来的对象会被提升到第2代对象。因为新对象的生存期较短,而老对象则倾向于继续存活。这样大部分新对象都会在容量最少的第0代回收中进行处理,从而提高了垃圾回收的性能。
同时也要注意尽量避免使用终结器Finalize。因为在newobj创建对象时会判断是否定义了Finalize方法,如果定义了,会把对象的引用添加到"终结链表"中。该对象在首次被垃圾回收时会被添加到"终结可达队列"中,但是在这次回收中对象不会被回收。同时特殊的CLR线程会清空队列同时执行队列中每个对象的Finalize方法释放资源。在下一次垃圾回收时,队列中的对象不再被根指向,也不在终结可达队列中,此时对象可被回收。可见,定义了Finalize方法的对象最少会经过两次回收才能被回收,并且代被提升了,所以不合理使用Finalize会影响应用程序性能。