C#是一种托管语言,意味着内存管理是由.NET运行时(CLR - Common Language Runtime)自动处理的。
栈(Stack)
栈是一种后进先出(LIFO, Last In First Out)数据结构。在C#中,栈主要用于存储值类型变量(如int、char、struct)和方法调用的局部变量。栈的特点是分配和释放速度快,因为只需要移动栈指针即可。
- 值类型:存储在栈上,它们的生命周期由包含它们的上下文决定。当方法调用结束时,它们会自动释放。如果是作为类的成员变量存在,那么存储在堆上,但它们本身仍然是值类型。
- 方法调用:每次方法调用会在栈上创建一个栈帧(Stack Frame),用于存储该方法的参数、局部变量和返回地址。当方法返回时,相应的栈帧会被销毁。
堆(Heap)(一般是指托管堆(Managed Heap))
堆是一种动态分配内存的区域,适用于存储引用类型(如类对象)。堆中的内存分配和释放是由垃圾回收器(GC)管理的。
- 引用类型:存储在堆上,变量指针存储在栈上,包含指向堆中对象的引用(地址),它们的生命周期由垃圾回收器决定。引用类型包括类实例、数组等。
-
内存管理:堆上的对象不会在超出作用域后立即销毁,而是由垃圾回收器根据对象的可达性(是否还有引用指向它)决定何时释放。
内存泄漏是说,有实际有引用指向的对象,这部分垃圾回收不会回收处理掉,但是软件的预期是该对象应当回收,没有意识到还有引用指向,这就是内存泄漏,因为软件不会使用该对象,垃圾回收也不会处理,这部分内存完全浪费了
非托管堆(Unmanaged Heap)
非托管理堆是由操作系统管理的内存区域,通常用于存储非托管代码(如C/C++代码)创建的对象。非托管内存的分配和释放需要由程序员手动控制(如通过Marshal.AllocHGlobal
和Marshal.FreeHGlobal
)。
- 内存泄漏风险:由于非托管内存不受垃圾回收器管理,程序员必须确保手动释放内存,否则会导致内存泄漏。
- 互操作性:在C#中,可以通过P/Invoke或COM与非托管代码进行互操作,需要特别注意内存管理。
大对象堆
大对象堆是托管堆的一部分,具体参见垃圾回收章节
装箱拆箱
装箱是将一个值类型(例如 int
、struct
等)转换为一个引用类型(例如 object
)的过程。这个过程涉及到将值类型的数据从栈(stack)上移动到堆(heap)上。
过程:
- 创建对象: 在堆上分配内存来存储值类型的数据。
- 复制值: 将值类型的实际数据从栈中复制到堆上新创建的对象中。
- 返回引用: 返回对堆上对象的引用。
堆栈和堆的变化:
-
栈:
num
作为值类型变量存在于栈上。 -
堆:
obj
是一个引用类型变量,它在堆上创建了一个包含num
值的新对象。
拆箱是将装箱的引用类型数据转换回值类型的过程。这涉及到将堆上的数据复制回栈中。
过程:
- 验证类型: 检查堆上的对象是否为目标值类型。
- 复制数据: 将堆上的值复制回栈上的值类型变量中。
堆栈和堆的变化:
-
栈:
num
作为值类型变量存在于栈上。 -
堆:
obj
在堆上仍然存在,直到不再被引用并被垃圾回收器回收。