该随笔受启发于《CLR Via C#(第三版)》第四章4.4运行时的相互联系

一、内存分配的几个区域

1、线程栈

局部变量的值类型 和 局部变量中引用类型的指针(或称引用)会被分配到该区域上(引用类型的一部分内存被分配到该区域内)。

该区域由系统管控,不受垃圾收集器的控制。当所在方法执行完毕后,局部变量会自动释放(引用类型只释放指针,而不释放指针指向的数据)。

堆栈的执行效率很高,但容量有限。

2、GC Heap(回收堆)

用于分配小对象(引用类型),如果引用类型的实例大小 小于85000个字节,则会被分配到该区域上。

3、LOH(Large Object Heap)

超过 85000个字节的大对象(引用类型)会被分配到该区域上。

LOH 和 GC Heap区别在于:当有内存分配或者回收时,垃圾收集器可能会对GC Heap进行压缩,而 LOH 不会被压缩,只是在垃圾回收时被回收。

二、栈桢(stack frame)

栈桢是实现函数调用的数据结构,从逻辑上看,栈桢是一个函数执行的环境(上下文),包括:函数参数、返回地址和函数局部变量。

注意:上述的“返回地址” 不是函数的返回值,而是完成函数调用后的CPU接下来要执行的代码的位置。

更多参见: 维基百科 call stack

三、浅拷贝 和 深拷贝(Clone、或克隆)

浅拷贝和深拷贝是内存拷贝的两种方式,在传参的过程中通常会发生内存拷贝并且是浅拷贝,比如下面代码、两个参数就是两次浅拷贝。

        public void Test1()
{
string name = "test1";
int size = 1;
Test2(name, size);// 两次浅拷贝
} public void Test2(string pName, int pSize)
{
}

 浅拷贝 前后的内存结果如下图所示:

浅拷贝对于值类型(例如:int),拷贝的是栈中的具体值。

浅拷贝对于引用类型(例如:string),拷贝的是栈中的引用、其新引用 所指向的 托管堆中的地址 还是原来的地址。

深拷贝和浅拷贝的区别在于对 堆中数据(即对象)的处理

浅拷贝只拷贝栈中的引用,不拷贝堆中的对象,新引用指向原有对象。

深拷贝会拷贝栈中的引用,并且会在堆中创建新的对象,新对象的属性值 “可能” 来源于原有对象(因为、在C#中实现深拷贝需要自己编写额外的代码,

即实现 ICloneable 接口,深拷贝的结果是不确定的。不过建议大家尽可能避免使用该接口),新引用地址指向新的对象。因此,深拷贝只针对于引用类型,

对于值类型没有太多的意义。

四、Demo

如果说前三项是做铺垫,那么该Demo算是正文了。

事先已经定义好的如下类:

    public class Data
{
public string Name;
public int Size;
}

假设代码即将调用Test1函数:

        public void Test1()
{
string name = "test1";
int size = 1; Data data = new Data() { Name = "test1", Size = 1 }; Test2(name, size, data); object obj = size;
int temp = (int)obj;
} public void Test2(string pName, int pSize, Data pData)
{
pName = "test2";
pSize = 2; pData.Name = "test2";
pData.Size = 2; pData = new Data() { Name = "test3", Size = 3 };
}

此时线程栈和托管堆内的情况如下图所示(图1):

1、开始调用Test1函数,这时会向栈中压入一个栈桢(stack frame)。

  栈桢包含3部分数据:

  1)函数参数,当然Test1函数没有参数。

  2)返回地址,在Test1函数中,以当前代码环境为例该地址没有什么实际意义不作说明。

  3)函数局部变量,此时Test1函数中的所有的局部变量都会被压入栈。 有如下五个局部变量会被压入栈:

string name, int32 size, Data data, object obj, int32 temp。(当然实际是六个局部变量,第六个是编译器自动生成的,在随笔结尾处再做解释。)

压入一个栈桢后,内存结果如下图所示(图2):

2、接下来执行Test1函数的代码,给name赋值,给size赋值,给data赋值。内存结果将会如下图所示(图3):

上图中的红线编译器生成的变量 将在随笔结尾出做解释。

3、接下来将调用Test2函数,这时还会向栈中压入一个栈桢。

栈桢包含3部分数据:

  1)函数参数,Test2函数有三个参数,三个参数都是浅拷贝。

  2)返回地址,该地址为调用Test2函数代码位置的下一行(或者称下一个指令)的位置,即:

obj = size; // 在调用Test1函数压入栈桢的时候已经完成了 object obj; 的操作

  完成函数调用后,将执行上述代码。

  3)函数局部变量,编译器会自动生成一个变量(随笔结尾处,将做出解释)

故,此时的内存结果如下图所示(图4):

两条橙色线是参数浅拷贝的结果。

4、 在执行完Test2函数的函数体,未返回Test1函数之前,内存结果如下图所示(图5):

红线 将在随笔结尾处做解释。

黄线 需要注意,string是特殊的引用类型,其外在表现和值类型一致,但其本质上还是引用类型。由于string的不可变性,对string的每一次赋值

  都会产生一个新的对象(如果新对象不存在),所以导致了黄线的出现。

浅绿线 我想这条线应该没有问题吧。

5、 完成Test2函数的调用,代码将返回到 Test2函数栈桢返回地址所示位置,在这一过程中 Test2函数栈桢 将被弹出,结果如下图所示(图6):

托管堆中的不被使用的对象将由GC进行自动回收,被回收的时间不确定。

6、 由3可以知,接下来将执行 obj = size; 的操作——装箱,从字面上可以这样理解:将值类型装进引用类型的箱子里。

大致的操作过程如下:

  1)首先、在堆中创建一个新的对象。(由于该操作导致装箱操作的性能降低)

  2)将size的值复制到对象中。

  3)最后将obj的引用指向新创建的对象。

最后的结果如下图所示(图7):

7、最后一个操作——拆箱

装箱,是将值类型装进引用类型的箱子里,在这一过程中,发生对象创建和内存复制。

然而拆箱,并没有那么复杂,也没有类似的“拆”的过程,只是将对象中的值类型读取出来(最耗时的操作应该是寻找值类型所在的内存地址的操作),相对于装箱,其性能要好很多。

最终结果如下图所示(图8):

五、 语法糖——对象初始化器

如下的两个代码片段是完全等效的:

            Data temp = new Data(); // 编译器自动生成的变量名不一定叫temp
temp.Name = "test1";
temp.Size = 1;
data = temp;
       data = new Data() { Name = "test1", Size = 1 };

第二种写法,可以看作是一种简写,编译器在编译的时候会将第二种写法的代码进行转化,转化成第一种写法。所以对象初始化器,C#3.0的语法新特性,

完全是一种语法糖,CLR没有起到任何作用。

当看到这的时候,再想想上面的红线 一切迎刃而解。

.NET 内存基础(通过内存体验类型、传参、及装箱拆箱)的更多相关文章

  1. java基础1.5版后新特性 自动装箱拆箱 Date SimpleDateFormat Calendar.getInstance()获得一个日历对象 抽象不要生成对象 get set add System.arrayCopy()用于集合等的扩容

    8种基本数据类型的8种包装类 byte Byte short Short int Integer long Long float Float double Double char Character ...

  2. Java String引起的常量池、String类型传参、“==”、“equals”、“hashCode”问题 细节分析

    在学习javase的过程中,总是会遇到关于String的各种细节问题,而这些问题往往会出现在Java攻城狮面试中,今天想写一篇随笔,简单记录下我的一些想法.话不多说,直接进入正题. 1.String常 ...

  3. 6个重要的.NET概念:栈,堆,值类型,引用类型,装箱,拆箱

    引言 本篇文章主要介绍.NET中6个重要的概念:栈,堆,值类型,引用类型,装箱,拆箱.文章开始介绍当你声明一个变量时,编译器内部发生了什么,然后介绍两个重要的概念:栈和堆:最后介绍值类型和引用类型,并 ...

  4. NET中的类型和装箱/拆箱原理

    谈到装箱拆箱,DebugLZQ相信给位园子里的博友一定可以娓娓道来,大概的意思就是值类型和引用类型的相互转换呗---值类型到引用类型叫装箱,反之则叫拆箱.这当然没有问题,可是你只知道这么多,那么Deb ...

  5. C#基础精华03(常用类库StringBuilder,List<T>泛型集合,Dictionary<K , V> 键值对集合,装箱拆箱)

    常用类库StringBuilder StringBuilder高效的字符串操作 当大量进行字符串操作的时候,比如,很多次的字符串的拼接操作. String 对象是不可变的. 每次使用 System. ...

  6. [No0000136]6个重要的.NET概念:栈,堆,值类型,引用类型,装箱,拆箱

    引言 本篇文章主要介绍.NET中6个重要的概念:栈,堆,值类型,引用类型,装箱,拆箱.文章开始介绍当你声明一个变量时,编译器内部发生了什么,然后介绍两个重要的概念:栈和堆:最后介绍值类型和引用类型,并 ...

  7. C#基础复习(2) 之 装箱拆箱

    参考资料 [1] @只增笑耳Jason的回答 https://www.zhihu.com/question/57208269 [2] <C# 捷径教程> 疑难解答 装箱和拆箱是什么? 何时 ...

  8. 深入C#内存管理来分析值类型&引用类型,装箱&拆箱,堆栈几个概念组合之间的区别

    C#初学者经常被问的几道辨析题,值类型与引用类型,装箱与拆箱,堆栈,这几个概念组合之间区别,看完此篇应该可以解惑. 俗话说,用思想编程的是文艺程序猿,用经验编程的是普通程序猿,用复制粘贴编程的是2B程 ...

  9. 以Integer类型传参值不变来理解Java值传参

    最近在写代码的时候出了一个错误,由于对值引用理解的不深,将Integer传入方法中修改,以为传入后直接修改Integer中的值就不用写返回值接收了,虽然很快发现了问题,但还是来总结一下 首先是代码: ...

随机推荐

  1. C#_闭包陷阱

    如果匿名方法(Lambda表达式)引用了某个局部变量,编译器就会自动将该引用提升到该闭包对象中. 即将for循环中的变量i修改成了引用闭包对象的公共变量i.这样一来,即使代码执行后离开了原局部变量i的 ...

  2. Prim 最小生成树算法

    Prim 算法是一种解决最小生成树问题(Minimum Spanning Tree)的算法.和 Kruskal 算法类似,Prim 算法的设计也是基于贪心算法(Greedy algorithm). P ...

  3. 简单一招实现json数据可视化

    开发一个内部功能时碰到的需求,要把json数据在页面上展示出来,平时浏览器会安装jsonView这样的扩展来看json数据,但是程序要用到的话该怎么办呢?今天在网上搜索的时候,发现了这个小技巧,分享一 ...

  4. Azure SQL Database Active Geo-Replication简介

    笔者在<迁移SQL Server 数据库到 Azure SQL 实战>一文中,介绍了如何把一个本地版的 SQL Server 数据库迁移到 Azure SQL Database.迁移虽然顺 ...

  5. spring事务管理器设计思想(一)

    在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (38) ------ 第七章 使用对象服务之动态创建连接字符串和从数据库读取模型

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第七章 使用对象服务 本章篇幅适中,对真实应用中的常见问题提供了切实可行的解决方案. ...

  7. 不知道张(zhāng)雱(pāng)是谁?你out了!

    张(zhāng)雱(pāng)是谁?也许你已经听说过了,也许你还没听说过呢,不过你一定听说过老刘——刘强东,没错,这二人是有关系的,什么关系,京东是老刘的,而张雱呢?张雱是京东旗下52家关联公司法人代 ...

  8. Bootstrap~学习笔记索引

    回到占占推荐博客索引 bootstrap已经用了有段时间了,感觉在使用上还是比较容易接受的,在开发人员用起来上,也还好,不用考虑它的兼容性,手机,平台,PC都可以有效的兼容. bootstrap官方a ...

  9. Android第一天

    ---恢复内容开始--- 一.配置JDK 第一步:右键我的电脑-属性-高级系统设置-环境变量 输入变量名和变量值: 环境变量名和变量值: 变量名:JAVA_HOME 变量值:D:\Java\jdk1. ...

  10. Hibernate框架的配置

    概念 持久化框架 把对象保存到数据库中,对数据的CURD操作 配置Hibernate 1.在项目中引入Hibernate的Jar包 在 WebContent/WEB-INF/lib 目录下 导入jar ...