在.NET框架包含一个托管堆,所有的.NET语言在分配引用类型对象都要使用它,像值类型这样的轻量级别对象始终分配在栈中,但是所有的类实例和数组都被生成在一个内存池中,这个内存池就是托管堆
垃圾收集器的托管的基本算法很简单:
- 将所有的托管内存标记为垃圾
- 寻找正在使用的内存快,并将他们标记为有效
- 释放所有没有被使用的内存块
- 整理堆以减少碎片
看上去很简单,但是垃圾回收器实际采用的步骤和堆管理系统的其他部分并非微不足道,其中常常涉及为提高性能而作的优化设计。举例来说,垃圾收集遍历整个内存池具有很高的开销。然而,研究表明大部分在托管堆上分配的对象只有很短的生存期,因此堆被分成三个段,称作generations。新分配的对象被放在generation 0中。这个generation是最先被回收的----在这个generation中最可能找到不再使用的内存,由于它的尺寸很小(小到足以放进处理器L2cache中),因此在它里面的回收将是最快和最高效的。
托管堆的另外一种优化操作于locality of reference股则有关。该规则表明,一起分配的对象经常被一起使用。如果对象们在堆中位置很紧凑的话,高速缓存的性能将会得到提高。由于托管堆的天性,对象们总是被分配在连续的地址上,托管堆总是保持紧凑,结果使得对象们始终彼此靠近,永远不会分的很远。这一点于标准堆提供的非托管代码形成了鲜明的对比,在标准堆中,堆很容易变成碎片,而且一起分配的对象经常分得很远。
还有一种优化是与大对象有关的,通常,大对象具有很长的生存期。当一个大对象在.NET托管堆中产生时,它被分配在堆的一个特殊部分中,这部分堆永远不会被整理。因为移动大对象所带来的开销超过了整理这部分堆所提高的性能。
栈与托管堆的不同
- 栈是内存中完全用于存储局部变量或成员(值类型数据)的高效区域,但其大小有限制。托管堆占内存比栈大得多,当访问速度较慢。托管堆只用于分配内存,一般有CLR来出来释放问题。
- 当创建值类型数据时,在栈上分配内存;当创建引用型数据时,在托管堆上分配内存并返回对象的引用。注意这个对象的引用,像其他局部变量一样也是保存在栈中,该引用指向的值则位于托管堆中。
- 如果创建一个包含值类型的引用类型,比如数组,其元素的值也是存放在托管堆中的某个地方,由使用该实体的变量引用;而值类型存储在使用它们的地方,有几处在使用,就有几个副本存在。对于引用类型,如果在声明变量的时候没有使用new运算符,运行时不会给它分配托管堆的内存空间,而是在栈伤给她分配一个包含null值的引用。对于值类型,运行时会给它分配栈上的空间,并且调用构造函数来初始化对象的状态。
CTS基本类型系统细节
栈和托管堆
通用类型系统(CTS)区分两种基本类型:值类型和引用类型。它们之间的根本区别在于它们在内存中的存储方式。.NET使用两种不同的物理内存快来存储数据------栈和托管堆:

值类型总是在内存中占用一个预定义的字节数(例如,int类型占4个字节,而string类型占用的字节数会根据字符串的长度而不同)。当生命一个值类型变量时,会在栈中分配适当大小的内存(除了引用类型的值类型成员外,如类的int字段)。内存中的这个空间用来存储变量所包含的值。.NET维护一个栈指针,它包含栈中下一个可用的内存空间的地址。当一个变量离开作用域时,栈指针指向下移动被释放变量所占有的字节数。所以它仍指向下一个可用地址。
引用变量也利用栈,但这时候栈包含的只是对另一个内存位置的引用,而不是实际值。这个位置是托管堆中的一个地址,和栈一样,他也维护一个指针,包含堆中下一个可用的内存地址。但是,堆不是先入后出的,因为对对象的引用可在我们的程序中传递(例如,作为参数传递给方法调用)。堆中的对象不会在程序的一个预定点离开作用域。为了在不适用的堆中分配的内存时将它释放。.NET定期执行垃圾回收集,垃圾收集递归检查应用程序中所有对象的引用。引用不再有效的对象使用的内存无法从程序中访问,该内存就可以回收。
类型层次结构
CTS定义了一种类型层次结构,该结构不仅仅描述了不同预定义类型,还指出了用户定义类型的层次结构中的位置

引用类型
引用类型包含一个指针,指向堆中存储对象本身的位置。因为引用类型只包含实际的值,对方法体内参数所做的任何修改都将影响传递给方法调用的引用类型的变量。
下图显示了声明一个字符串变量并把它作为参数传递给一个方法时所发生的事情。
string s1="something";
DoSomething(s1);
//.....
DoSomething(string s2)
{
//....
}

当声明一个字符变量s1时,一个值被压入栈中,它指向栈中的一个位置,在上图中,引用存放在地址1243044中,而实际的字符串存放在堆地址12662032中,当该字符串传递给一个方法中,在栈中对应输入参数声明了一个新的变量(这次是在地址1243032上),保存在引用变量,即堆中内存位置中的值被传递给这个新德变量。
委托是引用方法的一种引用类型,类似于c++中的函数指针(两者的主要区别于委托包括调用其方法的对象)。
预定义的引用类型
有两种引用类型在c#中受到了特别的重视,他们的c#别名和预定义值类型的c#别名很相像。第一种是object(c#别名是object,o小写)。这是所有值类型和引用类型的最终基类。因为所有的类型派生自object,所以可以把任何类型转换成Object,甚至值类型也可以转换。这个把值类型转换为Object的过程称为装箱。所有的值类型都派生自引用类型,在这件事看似矛盾的事情背后,装箱的作用不可或缺。
第二种是String类,字符串代表一个固定不变的Unicode字符序列,这种不变性意味着,一旦在堆中分配了一个字符串,他的值将永远不会改变,如果值该改变了,.NET就创建一个全新的String对象,并把它赋值给该变量,这意味着,字符串在很多方面都像值类型,而不像引用类型。如果把一个字符串传递给方法。然后在方法体内改变参数的值,这不会影响最初的字符串(当然,除非参数按引用传递的)。c#提供了别名String(s小写)来代表Ststem.String类,如果代码中使用String,必须在代码一开始添加Using System;这一行。使用内建的别名string则不需要using System;