我们都知道,C#是一门托管语言,我们写程序的时候,不需要手动对内存进行管理,很多时候,c#的GC机制会为我们解决一些相对繁琐的内存管理。那么,是否我们就不需要了解相应的内存管理机制了呢?其实不然,C# 虽然有垃圾自动回收机制;但是,要保证我们的程序高新能的运行,我们在声明创建变量时,也有一些需要注意的地方。今天就和大家分享一下,C#程序在CLR上运行时,其内存中的堆和栈。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190610182410755.gif)
C#中的主要类型
在定义代码的时候,会使用到一些c#的数据类型,通常在C#中主要使用的大概有四种类型:
值类型(ValueType)、引用类型(ReferenceType)、Pointer(指针类型)、指令(Instructions)
值类型
- bool
- byte
- char
- decimal
- double
- enum
- float
- int
- long
- sbyte
- short
- struct
- uint
- ulong
- ushort
引用类型
- class
- interface
- delegate
- object
- string
内存的分配机制
1. 我们在程序中声明的变量只会在堆或者栈上进行内存分配,变量要么分配的堆内存上,要么分配的栈内存上。
2. 堆内存主要用来存储较大且存储时间较长的数据,而栈内存主要用来存储较小和短暂的数据。
3. 通常情况下,我们都认为,引用类型总是被分配到堆内存上。
4. 而值类型和指针类型总是根据定义他们的位置来进行分配内存空间,并不一定是值类型就被分配到栈内存上。
这么说可能有些不太容易理解,我们举一个例子。
一、 声明一个函数如下:
public int ReturnValue()
{
int x = new int(); //int x=3;值类型
x = 3;
int y = new int(); //int y=x;值类型
y = x;
y = 4; //y=4
return x;
}
二、 定义一个类如下:
public class MyInt{
public int MyValue;
}
三、 再声明一个函数如下:
public int ReturnValue2()
{
MyInt x = new MyInt(); // 声明一个MyInt 引用类型的实例
x.MyValue = 3;
MyInt y = new MyInt(); // 声明一个MyInt 引用类型的实例
y = x;
y.MyValue = 4;
return x.MyValue;
}
步骤一中的函数的内存分配:
public int ReturnValue()
{
int x = new int(); //int x=3;值类型
x = 3;
int y = new int(); //int y=x;值类型
y = x;
y = 4; //y=4
return x;
}
如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190610182514120.gif)
当执行ReturnValue方法时,变量X(值类型)和变量Y(值类型)被分配到栈内存上。当函数执行完毕,刚才分配的X 和Y占用的内存会被回收;我们都知道栈的一个特点就是后进先出,只能在栈的一端对栈空间进行操作。这个函数中声明的变量X和Y均是局部变量,当函数结束后,其占用的内存空间会被返还。那么我们刚才所说的值类型不一定被分配到栈内存上是怎么回事呢?我们接着往下看。
步骤三中函数的内存分配如下:
public int ReturnValue2(int value)
{
MyInt x = new MyInt(); // 声明一个MyInt 引用类型的实例
x.MyValue = 3;
MyInt y = new MyInt(); // 声明一个MyInt 引用类型的实例
y = x;
y.MyValue = 4+value;
return x.MyValue;
}
如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190610182641326.gif)
当执行ReturebValue2时,ReturebValue2的参数value会被分配到栈内存上,由于MyInt是一个引用类型,所以他会被分配到堆内存上,并且会在栈中生成一个指针(指针中存储MyInt 实例的内存地址),当ReturenValue2方法执行完毕时,栈上内存会被清理,也就是栈中的两个指针会被清理,但是堆中的MyInt依然存在。那么,堆中的MyInt什么时候会被清理呢。当我们的程序需要更多的堆空间时,会触发GC(垃圾回收机制),暂停所有线程,找出所有没有被引用的对象,进行清理,此时,堆中的MyInt才会被回收。
关于垃圾回收(GC)
我们需要知道,当一个变量不再使用或者不在被引用时,该部分所占用的内存可以被回收到内存池中被再次使用,栈内存的回收时非常快速的;但是,对于堆内存,我们之前说过,堆内存主要用来存储较大且存储时间较长的数据,因此,堆内存的回收并没有那么及时,所以我们通常所说的垃圾回收,主要是指堆上内存的分配与回收。
栈内存的分配和回收机制
栈内存的分配与回收十分的快捷简单,因为栈内存上存储的是短暂或者较小的变量,内存分配和回收会以一种顺序和大小可控的形式进行。同时,栈内存的运行方式和我们平时所接触的数据结构————栈是一样的,数据的进出都固定在一端进行。
堆内存的分配和回收机制
堆内存的分配和回收方式相对于栈内存来说,相对复杂一些。因为堆内存上不仅可以存储一些周期短,占用内存小的数据,也可以存储各种类型的数据,其内存分配和回收我们并不可控。但可以通过改良代码结构来避免。
析构函数和垃圾回收器在C# CLR中的运用
C# (CLR)中提供一种新的内存管理机制,资源的释放是通过“垃圾回收器”自动完成的,一般不需要用户进行干预,但有些特殊情况下会使用到析构函数在C# 中释放非托管资源。
资源通过”垃圾回收器“来释放需要注意以下几个地方:
- 值类型和引用类型的引用 其实是不需要“垃圾回收器”来释放内存的,因为值类型和引用类型的引用都保存在栈中,出了作用域之后,其所占用内存会被自动释放。**
- 只有引用类型的引用所指向的实例对象才保存在堆(heap)中,而堆因为是一个自由的存储空间,所以它并没有向“栈” 那样有生存期(“栈"的元素弹出后就代表生存期结束,即所占用内存被释放);当然,需要注意的是:”垃圾回收机制”,只对堆内存起作用。
释放非托管资源时,使用析构函数进行,比如:
public class ResourceHolder {
~ResourceHolder () {
// 这里是清理非托管资源的用户代码段
}
}
更多内容,欢迎关注:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019092309095774.jpg)