图解C#值类型与引用类型的内存分配情况

您所在的位置:网站首页 储存内存大小的区别 图解C#值类型与引用类型的内存分配情况

图解C#值类型与引用类型的内存分配情况

2024-07-17 12:04:07| 来源: 网络整理| 查看: 265

文章目录 前言一、类型系统之值类型和引用类型1.值类型与引用类型的派生关系2.值类型与引用类型的主要区别3.值类型和引用类型的使用场合 二、内存的逻辑划分之栈和堆1.栈的特征2.栈的结构3.堆的特征4.堆的结构 三、代码运行时的内存分配情况1.变量和对象在内存中的分配2.方法参数在栈中的分配3.特殊的引用类型“System.String” 总结

前言

关于C#的值类型和引用类型,在面试中经常被问到的问题就是:值类型与引用类型的内存分配有哪些区别,大多数同学只能回答到值类型存储在栈上,引用类型存储在堆上。具体是如何分配的,栈和堆的结构特征是怎样的,了解的较少。今天我们通过这篇文章,来彻底搞懂C#值类型与引用类型的内存分配情况。

一、类型系统之值类型和引用类型

C#的类型一共分为两类,一种是值类型(Value Type),一类是引用类型(Reference Type)。

在这里插入图片描述

1.值类型与引用类型的派生关系

在这里插入图片描述 值类型和引用类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承System.ValueType。System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

2.值类型与引用类型的主要区别 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值;引用类型可以派生出新的类型,而值类型不能;引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型);值类型总是分配在它声明的地方,作为字段时,跟随其所属的变量(实例)存储在堆上,作为局部变量时,存储在栈上。引用类型存储在堆中。类型实例化的时候,会在堆中开辟一部分空间存储类的实例,类实例的引用(指针)还是存储在栈中。 3.值类型和引用类型的使用场合

值类型:在内存管理方面具有更好的效率,但不支持多态,不能派生新的类型,适合用做存储数据的载体;

引用类型:支持多态,可以派生新的类型,适合用于定义应用程序的行为。

二、内存的逻辑划分之栈和堆

C#程序在CLR上运行时,内存从逻辑上划分两大块:栈、堆,这两个基本元素组成了C#程序的运行环境。

栈,在程序运行的时候,每个线程(Thread)都会维护一个自己的专属线程堆栈。把它想像成叠在一起的盒子(像搭积木一样)。每一次调用一个方法就会在最上面叠一个盒子,用来跟踪程序运行情况。我们只能使用栈中叠在最上面的盒子里的东西。当最上面的盒子里的代码执行完毕(如方法执行完成),就把它扔掉并继续去使用下一个盒子。

堆,是程序在运行的时候请求操作系统分配给自己的内存空间,可以想象成一个仓库,储存着我们使用的各种对象等信息,跟栈不同的是他们被调用完毕不会立即被清理掉。

1.栈的特征 栈空间比较小(每个线程只有一个栈,占用1MB,栈内存溢出抛出StackOverflowException),但是读取速度快;数据只能从栈的顶端插入或删除,是连续存储的,把数据放到栈顶称为入栈,从栈顶删除数据称为出栈;存放方法的参数、局部变量、返回地址等值,当一个方法执行完毕后立刻自动清除。 2.栈的结构 栈帧,每个方法执行都会分配一块独立的内存空间来存储方法运行需要的数据;栈帧也是后入先出的方式进入和弹出线程栈。 3.堆的特征 堆空间比较大(32位最多分配1.5GB,64位最多分配8TB,堆内存溢出抛出OutOfMemoryException),但是读取速度慢;数据存储不连续,与栈不同,堆里的内存能够以任意顺序存入和移除;存放引用类型的对象,通过GC清理。 4.堆的结构 堆中包含(至少)三个程序域,以及它们自带的加载堆和其他零部件;加载堆(loader heap)存在于每一个程序域中,存放 CLR 自己的类型系统以及用户定义的类型对象;GC 堆(GC heap),垃圾收集器的处理对象。它分为 0, 1, 2 代三块区域,越高代的堆大小越大;JIT 代码堆,用来存放 JIT 之后的本地代码。

参考链接:CLR如何创建运行时对象

三、代码运行时的内存分配情况

下面我们以实际的代码运行过程为例,详细介绍C#代码在运行时的内存分配情况。

1.变量和对象在内存中的分配

示例代码:

class TestClass { public int x; public static string y; } void Test1() { var a=1; var b=new TestClass(); var c=a; var d=b; var e=d.x; var f=TestClass.y; }

内存分配情况 在这里插入图片描述

Test1()方法被调用时,系统为该方法创建一个栈桢,用于存储该方法使用到的值类型的变量、指针、调用其他方法的返回地址等;

方法执行到 var a=1 时,首先入栈,变量a的值1存储在栈中,栈的起始地址为0x000000671b77e5a4;

方法执行到 var b=new TestClass() 时,会在堆中开辟一块儿内存用于存储TestClass实例对象,然后变量b入栈,变量b的值为TestClass实例对象的引用(实际上存储的是TestClass实例在堆上的内存地址,也就是指针);

方法执行到 var c=a 时,将变量c压入栈,因为a是值类型,所以对变量a的值进行拷贝赋值给c;

方法执行到 var d=b 时,将变量d压入栈,因为b是引用类型,所以将变量b引用的地址赋值给变量d,仍然指向堆内存中的TestClass实例对象;

方法执行到 var e=d.x 时,将变量e压入栈,因为x字段是值类型,所以将x的实际值0(int类型初始化的默认值为0)赋值给e;

方法执行到 var f=TestClass.y 时,将变量f压入栈,因为y字段是引用类型,所以f变量的值为y字段的引用。

2.方法参数在栈中的分配

示例代码:

class TestClass { public int x; public int sum(int i,int j){ return i+j; } } void Test1() { var a=new TestClass(); int b = 0; b=a.sum(1,2); }

内存分配情况: 在这里插入图片描述

方法执行到 var a=new TestClass() ,会在堆中开辟一块儿内存用于存储TestClass实例对象,然后变量a入栈,变量a的值为TestClass实例对象的引用(实际上存储的是TestClass实例在堆上的内存地址,也就是指针);

方法执行到 int b = 0 ,将局部变量b压入栈,因为b是值类型,所以值0存储在栈中;

方法执行到 b=a.sum(1,2) ,首先两个int类型实参1,2分别入栈,并将sum方法的返回地址压入栈,sum方法执行结束之后应返回至该位置。

3.特殊的引用类型“System.String”

特性一:字符串是不可变的,字符串一经创建便不能更改,不能变长、变短或修改其中的任何字符。

特性二:字符串驻留(字符串池化),CLR可通过一个String对象共享多个完全一致的String内容,这样能减少系统中字符串的数量,从而节省内存。String的驻留机制实际上是在SystemDomain中进行的。 当CLR被加载之后,会在SystemDomain对应的managed heap中创建一个Hashtable,Hashtable中记录了所有在代码中使用字面量声明的字符串实例的引用,Hashtable的Key为字符串本身,Value为字符串对象的地址。

示例代码:

static void Main(string[] args) { //申请一块堆内存,把地址放在Hashtable的key为hello的元素中 string str1 = "hello"; //由于上一句已经创建了key为hello的元素,所以不需要申请新的堆内存 string str2 = "hello"; //编译成MSIL语言时 已经与string str3 = "hello"一样了 string str3 = "" + "e" + "l" + "l" + "o"; //自己显示进行new string str4 = new string(new char[] { 'h', 'e', 'l', 'l', 'o' }); //申请一块堆内存,把地址放在Hashtable的key为hello2的元素中 string str5 = "hello2"; //True 引用同一块堆内存 Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); //True 也是引用同一块堆内存 Console.WriteLine(object.ReferenceEquals(str1, str3).ToString()); //False 引用了不同的堆内存 Console.WriteLine(object.ReferenceEquals(str1, str4).ToString()); // 先从Hashtable中检索是否有重复的key ,检索到了hello2,所以不需要申请新的堆内存 str2 = "hello2"; //False str2与str1已经不引用同一个堆 Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); //True 变成与str5引用同一个堆内存 Console.WriteLine(object.ReferenceEquals(str2, str5).ToString()); // 控制台输入两个相同的字符串 str1 = Console.ReadLine(); str2 = Console.ReadLine(); //False 因为 str1 和 str2 两个变量并非字面量声明的字符串,所以不会触发字符串驻留机制 Console.WriteLine(object.ReferenceEquals(str1, str2).ToString()); Console.ReadLine(); } 总结

不理解值类型和引用类型区别的同学,可能会给代码引入诡异的bug或性能问题,通过这篇文章讲解了值类型与引用类型的区别,栈与堆的结构特征,代码运行时的内存分配情况,并配以示例代码及相关图形说明,尽可能形象的讲解了值类型与引用类型,希望能给大家带来帮助。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


    图片新闻

    实验室药品柜的特性有哪些
    实验室药品柜是实验室家具的重要组成部分之一,主要
    小学科学实验中有哪些教学
    计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
    实验室各种仪器原理动图讲
    1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
    高中化学常见仪器及实验装
    1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
    微生物操作主要设备和器具
    今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
    浅谈通风柜使用基本常识
     众所周知,通风柜功能中最主要的就是排气功能。在

    专题文章

      CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭