发布时间:2023-02-11 13:30
在讲栈和堆之前,我们先看看值类型和引用类型:
类别 | 描述 |
---|---|
值类型 | 基本数据类型 枚举类型 结构类型 |
引用类型 | 类 接口 数组 |
1)值类型:值类型总是分配在它声明的地方,作为局部变量时,存储在栈上;作为类对象的字段时,则跟随此对象存储在堆中。
2)引用类型:引用类型存储在堆中。类型实例化的时候,会在堆中开辟一部分空间存储类的实例。类对象的引用(地址)还是存储在栈中。
class Program
{
static void Main(string[] args)
{
byte b = 0xff;//byte类型 在栈中占 1个字节
UInt16 c = 0xffff;//uint16类型 在栈中占 2个字节
//person:对象的引用,存储在栈中
//new Person():对象的实例,存储在堆中
Person person = new Person();//引用类型
//a:局部变量,存储在栈中
int a = 10;//值类型
Console.ReadKey();
}
}
public class Person
{
//类的字段,跟随此类存储在堆中
public int Age { get; set; }//值类型
}
上面写了一些很简单的代码,我们运行起来并查看了变量在栈中的地址:
&b
0x001af368
*&b: 0xff
&c
0x001af364
*&c: 0xffff
&person
0x001af360
*&person: 0x02562390
&a
0x001af35c
*&a: 0x0000000a
我们发现了几条规律:
1)先声明的变量在栈中的地址比较大,后声明的变量在栈中的地址比较小
2)在栈中,对于值类型来说,那块地址存储的就是变量的值;而对于引用类型来说,存储的是对堆的引用(在堆中的地址)
栈的结果是先进后出。与生命周期相关,越靠近栈底部的(栈地址越大的声明周期越长)
栈地址是从高往低分配的,按照先后定义的顺序依次压入栈中,相邻变量的地址之间不会存在其他变量。
栈是有操作系统自动分配释放的(作为程序员,你不需要显示对它做任何事),用于存储变量的值、程序执行的当前环境、方法的参数和类型的引用等。
堆里的内存能够任意顺序的存入和移除,地址一般是从低往高分配。
堆里的数据由CLR的自动GC在判断出程序的代码将不会再访问某项数据项时,自动清除无主的堆对象。
在程序运行的时候,每一个线程都会维护一个自己的专属线程堆栈(就是我们说的栈)。当一个方法被调用的时候,主线程开始在所属程序集的元数据中,查找被调用方法,然后通过JIT即时编译并把结果(一般是本地CPU指令)放在栈顶,CPU通过总线从栈顶取指令,驱动程序以执行下去。
栈通常存储着我们代码执行的步骤,而堆上存放的则多是对象、数据等。在调用方法的时候,内存从栈的顶部开始分配,保存和方法关联的一些数据项,这块内存叫做方法的栈帧(stack frame)。栈帧包含的内存保存如下内容:
1)返回地址,也就是在方法退出的时候继续执行的位置。
2)这些参数分配的内存,也就是方法的值参数,或者还可能时参数数组。
3)各种和方法调用相关的其他管理数据项。
在方法调用时,整个栈帧都会压入栈;在方法推出的时候,整个栈帧都会从栈顶弹出,弹出栈帧有的时候也叫做栈展开。
栈内存无需我们管理,也不受GC管理。当栈顶元素使用完毕,立马释放。而堆则需要GC清理,它像一个仓库,存储着我们使用的各种对象信息,跟栈不同的是他们被调用完毕不会立即被清理掉。