该随笔受启发于《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. 发现meta有个刷新页面的办法。

    meta是html中不可缺少的一个标签,它的应用以方便浏览器搜索并分类当前网页的内容. meta总是放在head标签的第一个位置.今天我在复习前端知识的时候,在网上发现了用meta刷新网页的好办法. ...

  2. 我的第一段jQuery代码

    说起 jQuery,很多人可能觉得,不算什么,就是个js类库.而,对于我,下面这几行代码,是一个新的开始. 多年来,我一直在使用MooTools ,他面向对象,写起来结构清晰分明,都是在原生js的基础 ...

  3. EasyPR--一个开源的中文车牌识别系统

    我正在做一个开源的中文车牌识别系统,Git地址为:https://github.com/liuruoze/EasyPR. 我给它取的名字为EasyPR,也就是Easy to do Plate Reco ...

  4. ASP.NET MVC 控制器激活(二)

    ASP.NET MVC 控制器激活(二) 前言 在之前的篇幅中,用文字和图像来表示了控制器的激活过程,描述的角度都是从框架默认实现的角度去进行描述的,这样也使得大家都可以清楚的知道激活的过程以及其中涉 ...

  5. Outlook HTML渲染引擎

    OutLook始终不离不弃 是不是很讨厌为Email代码兼容Outlook? 太遗憾了!虽然光都有尽头,但Outlook始终存在. 为了应付Email的怪癖,我们花了很多时间测试,确保我们搞定了所有O ...

  6. 【VC++技术杂谈003】打印技术之打印机状态监控

    在上一篇博文中我主要介绍了如何获取以及设置系统的默认打印机,本文将介绍如何对打印机状态进行实时监控,记录下所打印的文档.打印的份数以及打印时间等打印信息. 1.打印机虚脱机技术 在正式介绍如何对打印机 ...

  7. Service基础使用

    Service基础使用 之前的文章一直介绍Activity的使用,很多知识和用法单一的配合Activity使用,这次将总结Android四大组件之二--Service. 本文将要介绍以下内容: Ser ...

  8. windows命令——taskkill

    C:\Users\Administrator>taskkill /? TASKKILL [/S system [/U username [/P [password]]]] { [/FI filt ...

  9. Hibernate

  10. 2013 duilib入门简明教程 -- 复杂控件介绍 (13)

      首先将本节要介绍的控件全部拖到界面上,并调整好位置,如图:           然后将Name属性改成其他名字,          不能是[控件名+UI+数字]这种,因为这是DuiDesigner ...