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均是局部变量,当函数结束后,其占用的内存空间会被返还。那么我们刚才所说的值类型不一定被分配到栈内存上是怎么回事呢?我们接着往下看。

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

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 () {
                // 这里是清理非托管资源的用户代码段
            }
        }

更多内容,欢迎关注:


在这里插入图片描述

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值