C# (CLR) 中内存分配解析

我们都知道,C#是一门托管语言,我们写程序的时候,不需要手动对内存进行管理,很多时候,c#的GC机制会为我们解决一些相对繁琐的内存管理。那么,是否我们就不需要了解相应的内存管理机制了呢?其实不然,C# 虽然有垃圾自动回收机制;但是,要保证我们的程序高新能的运行,我们在声明创建变量时,也有一些需要注意的地方。今天就和大家分享一下,C#程序在CLR上运行时,其内存中的堆和栈。

 

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;  
}  

当执行ReturnValue方法时,变量X(值类型)和变量Y(值类型)被分配到栈内存上。当函数执行完毕,刚才分配的X 和Y占用的内存会被回收;我们都知道栈的一个特点就是后进先出,只能在栈的一端对栈空间进行操作。这个函数中声明的变量X和Y均是局部变量,当函数结束后,其占用的内存空间会被返还。那么我们刚才所说的值类型不一定被分配到栈内存上是怎么回事呢?我们接着往下看。

扫描二维码关注公众号,回复: 8573595 查看本文章

步骤三中函数的内存分配如下:

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;  
}

当执行ReturebValue2时,ReturebValue2的参数value会被分配到栈内存上,由于MyInt是一个引用类型,所以他会被分配到堆内存上,并且会在栈中生成一个指针(指针中存储MyInt 实例的内存地址),当ReturenValue2方法执行完毕时,栈上内存会被清理,也就是栈中的两个指针会被清理,但是堆中的MyInt依然存在。那么,堆中的MyInt什么时候会被清理呢。当我们的程序需要更多的堆空间时,会触发GC(垃圾回收机制),暂停所有线程,找出所有没有被引用的对象,进行清理,此时,堆中的MyInt才会被回收。

关于垃圾回收(GC)

我们需要知道,当一个变量不再使用或者不在被引用时,该部分所占用的内存可以被回收到内存池中被再次使用,栈内存的回收时非常快速的;但是,对于堆内存,我们之前说过,堆内存主要用来存储较大且存储时间较长的数据,因此,堆内存的回收并没有那么及时,所以我们通常所说的垃圾回收,主要是指堆上内存的分配与回收。

栈内存的分配和回收机制
栈内存的分配与回收十分的快捷简单,因为栈内存上存储的是短暂或者较小的变量,内存分配和回收会以一种顺序和大小可控的形式进行。同时,栈内存的运行方式和我们平时所接触的数据结构————栈是一样的,数据的进出都固定在一端进行。

堆内存的分配和回收机制
堆内存的分配和回收方式相对于栈内存来说,相对复杂一些。因为堆内存上不仅可以存储一些周期短,占用内存小的数据,也可以存储各种类型的数据,其内存分配和回收我们并不可控。但可以通过改良代码结构来避免。

析构函数和垃圾回收器在C# CLR中的运用
C# (CLR)中提供一种新的内存管理机制,资源的释放是通过“垃圾回收器”自动完成的,一般不需要用户进行干预,但有些特殊情况下会使用到析构函数在C# 中释放非托管资源。

资源通过”垃圾回收器“来释放需要注意以下几个地方:

值类型和引用类型的引用 其实是不需要“垃圾回收器”来释放内存的,因为值类型和引用类型的引用都保存在栈中,出了作用域之后,其所占用内存会被自动释放。**
只有引用类型的引用所指向的实例对象才保存在堆(heap)中,而堆因为是一个自由的存储空间,所以它并没有向“栈” 那样有生存期(“栈"的元素弹出后就代表生存期结束,即所占用内存被释放);当然,需要注意的是:”垃圾回收机制”,只对堆内存起作用。
释放非托管资源时,使用析构函数进行,比如:

public class ResourceHolder {
            ~ResourceHolder () {
                // 这里是清理非托管资源的用户代码段
            }
        }

版权声明:本文为CSDN博主「Sun.ME」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/JianZuoGuang/article/details/91391005

猜你喜欢

转载自www.cnblogs.com/litubin/p/12188631.html