.Net将变量的类型分为引用类型和值类型两大类。使用int、double等数值类型所定义的变量属于值类型,除此之外,枚举类型和结构也属于值类型。类类型的变量属于引用类型,他们可以引用一个真实的对象。
.Net中的引用类型分为:类类型、接口类型、数组类型和委托类型。
值类型变量与引用类型变量的内存分配模型是不一样的,为此先区分下两种不同类型的内存区域:线程堆栈和托管堆
线程堆栈和托管堆
每个正在运行的程序都对应一个进程,在一个进程内部可以有一个或多个线程,每个线程都拥有一块内存,称为“线程堆栈”,大小为1M,用于保存自身的一些数据,比如线程函数中定义的局部变量、线程函数调用时传送的参数值等,这部分内存区域的分配与回收不需要程序员干涉。如下代码
void func()
{
int i = 100;
........
return;
}
CLR在执行函数func时会在线程堆栈中为它的局部变量i分配一个4字节的空间,并初始化为100,然后CLR执行剩余的代码,如果函数中还定义了其他局部变量,还会在线程堆栈中分配新的存储空间给这些局部变量。
当CLR执行函数func的return语句时,函数执行结束,CLR将负责回收分配给此函数的局部变量的内存,这一工作是自动进行的,大多数情况下程序员是不必理会的。
值类型变量所占用的内存单元是在线程堆栈中分配的。
另一块内存区域称为“堆”,在.Net这种托管环境下,堆由CLR进行管理,所以称为“托管堆”。
当用new关键字创建对象的时候,分配给对象的内存单元就位于托管堆中。
引用类型变量引用的对象所占的内存单元是在托管堆中分配的
引用类型变量的内存模型
测试代码如下:
class MyClass
{
public int Value;
public int[] Numbers = new int[10];
}
static void Main(string[] args)
{
MyClass obj;
obj = new MyClass();
}
代码中先是创建了一个引用类型变量obj,此时默认值为null,没有引用任何对象。下面用new关键字在托管堆中创建了一个NyClass类型的对象,此时obj的值为对象的首地址。此时的对象内存模型如下图所示。
引用类型变量obj生存于线程堆栈中,因为它是Main方法的局部变量,而MyClass对象生存于托管堆中,obj所代表的线程堆栈中的内存单元保存的是托管堆中NyClass对象的首地址。
由于引用类型变量和它所引用的对象生存于不同的世界,因此这两者其实是独立的,引用类型变量被销毁(比如定义它的方法执行结束时)并不意味它所引用的对象也会被销毁,这一对象有可能会继续生存一个相当长的时间。
如果是对象数组呢,它的内存模型是如何的呢?
MyClass[] objs;
objs = new MyClass[10];
创建完对象的内存模型如图:
由于数组对象本身是引用类型,所有不管数组元素是值类型还是引用类型,它所占用的内存都在托管堆上分配。