转载:http://blog.csdn.net/Zevin/article/details/5731965

线程堆栈:简称栈 Stack
托管堆: 简称堆 Heap

使用.Net框架开发程序的时候,我们无需关心内存分配问题,因为有GC这个大管家给我们料理一切。如果我们写出如下两段代码:
代码段1:

public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}

代码段2:

public class MyInt
{
public int MyValue;
}
public MyInt AddFive(int pValue)
{
MyInt result = new MyInt();
result.MyValue = pValue + 5;
return result;
}

问题1:你知道代码段1在执行的时候,pValue和result在内存中是如何存放,生命周期又如何?代码段2呢?

要想释疑以上问题,我们就应该对.Net下的栈(Stack)和托管堆(Heap)(简称堆)有个清楚认识,本立而道生。如果你想提高程序性能,理解栈和堆,必须的!

本文就从栈和堆,类型变量展开,对我们写的程序进行庖丁解牛。

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

一,栈 vs 堆:区别?

        栈通常保存着我们代码执行的步骤,如在代码段1中 AddFive()方法,int pValue变量,int result变量等等。而堆上存放的则多是对象,数据等。(译者注:忽略编译器优化)我们可以把栈想象成一个接着一个叠放在一起的盒子。当我们使用的时候,每次从最顶部取走一个盒子。栈也是如此,当一个方法(或类型)被调用完成的时候,就从栈顶取走(called a Frame,译注:调用帧),接着下一个。堆则不然,像是一个仓库,储存着我们使用的各种对象等信息,跟栈不同的是他们被调用完毕不会立即被清理掉。

如图1,栈与堆示意图

(图1)

栈内存无需我们管理,也不受GC管理。当栈顶元素使用完毕,立马释放。而堆则需要GC(Garbage collection:垃圾收集器)清理。

二,什么元素被分配到栈?什么被分配到堆?

当我们程序执行的时候,在栈和堆中分配有四种主要的类型:值类型,引用类型,指针,指令。

值类型:

在C#中,继承自System.ValueType的类型被称为值类型,主要有以下几种(CLR2.0中支持类型有增加):

    * bool

    * byte

    * char

    * decimal

    * double

    * enum

    * float

    * int

    * long

    * sbyte

    * short

    * struct

    * uint

    * ulong

    * ushort

引用类型:

以下是引用类型,继承自System.Object:

    * class

    * interface

    * delegate

    * object

    * string

指针:

在内存区中,指向一个类型的引用,通常被称为“指针”,它是受CLR( Common Language Runtime:公共语言运行时)管理,我们不能显示使用。需要注意的是,一个类型的引用即指针跟引用类型是两个完全不同的概念。指针在内存中占一块内存区,它本身只代表一个内存地址(或者null),它所指向的另一块内存区才是我们真正的数据或者类型。如图2:

(图2)

指令:

后文对指令再做介绍。

三,如何分配?

我们先看一下两个观点:

观点1,引用类型总是被分配在堆上。(正确?)

观点2,值类型和指针总是分配在被定义的地方,他们不一定被分配到栈上。(这个理解起来有点难度,需要慢慢来)

上文提及的栈(Stack),在程序运行的时候,每个线程(Thread)都会维护一个自己的专属线程堆栈。

当一个方法被调用的时候,主线程开始在所属程序集的元数据中,查找被调用方法,然后通过JIT即时编译并把结果(一般是本地CPU指令)放在栈顶。CPU通过总线从栈顶取指令,驱动程序以执行下去。

下面我们以实例来详谈。

还是我们开篇所列的代码段1:

 

public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}

当AddFive方法开始执行的时候,方法参数(parameters)则在栈上分配。如图3:

(图3)

注意:方法并不在栈中存活,图示仅供参考。

接着,指令指向AddFive方法内部,如果该方法是第一次执行,首先要进行JIT即时编译。如图4:

(图4)

当方法内部开始执行的时候,变量result被分配在栈上,如图5:

(图5)

方法执行完毕,而且方法返回后,如图6所示:

(图6)

在方法执行完毕返回后,栈上的区域被清理。如图7:

(图7)

以上看出,一个值类型变量,一般会分配在栈上。那观点2中所述又做何理解?“值类型和指针总是分配在被定义的地方,他们不一定被分配到栈上”。

原因就是如果一个值类型被声明在一个方法体外并且在一个引用类型中,那它就会在堆上进行分配。

还是代码段2:

public class MyInt
{
public int MyValue;
}
public MyInt AddFive(int pValue)
{
MyInt result = new MyInt();
result.MyValue = pValue + 5;
return result;
}

当线程开始执行AddFive方法的时候,参数被分配到栈上,如图8所示:

(图8)

由于MyInt是一个引用类型,所以它被分配到堆上,并且在栈中生成一个指针(result),如图9:

(图9)

AddFive方法执行完毕时的情况如图10:

(图10)

栈上内存被清理,堆中依然存在,如图11:

(图11)

当程序需要更多的堆空间时,GC需要进行垃圾清理工作,暂停所有线程,找出所有不可达到对象,即无被引用的对象,进行清理。并通知栈中的指针重新指向地址排序后的对象。现在我们应该知道,了解栈和堆,对我们开发出高性能程序的重要性。当我们使用引用类型的时候,一般是对指针进行的操作而非引用类型对象本身。但是值类型则操作其本身。

接下来,我们用例子说明这一点。

例1:

public int ReturnValue()
{
int x = new int();
x = 3;
int y = new int();
y = x;
y = 4;
return x;
}

执行结果为3,稍作修改:

例2:

public class MyInt
{
public int MyValue;
}
public int ReturnValue2()
{
MyInt x = new MyInt();
x.MyValue = 3;
MyInt y = new MyInt();
y = x;
y.MyValue = 4;
return x.MyValue;
}

执行结果为4。

我们来分析下原因,其实例1的跟以下代码所起效用一样:

public int ReturnValue()
{
int x = 3;
int y = x;
y = 4;
return x;
}

如图12所示,在栈上x和y分别占用一块内存区,互不干扰。

(图12)

而例2,与以下代码所起效用一样:

public int ReturnValue2()
{
MyInt x;
x.MyValue = 3;
MyInt y;
y = x;
y.MyValue = 4;
return x.MyValue;
}

如图13所示,

(图13)

栈上的指针x和y指向堆上同一个区域,修改其一必会改变堆上的数据。

C#中堆和栈的区别分析(有待更新总结2)的更多相关文章

  1. C#中堆和栈的区别分析(有待更新总结)

    转载:http://blog.csdn.net/zevin/article/details/5721495 一.预备知识-程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区 ...

  2. C#中堆和栈的区别分析

    线程堆栈:简称栈 Stack托管堆: 简称堆 Heap 使用.Net框架开发程序的时候,我们无需关心内存分配问题,因为有GC这个大管家给我们料理一切.如果我们写出如下两段代码: 1 代码段1: 2 3 ...

  3. C语言中堆和栈的区别

    原文:http://blog.csdn.net/tigerjibo/article/details/7423728 C语言中堆和栈的区别 一.前言: C语言程序经过编译连接后形成编译.连接后形成的二进 ...

  4. 转载:C++中堆和栈的区别

    C++中堆和栈的区别,自由存储区.全局/静态存储区和常量存储区     文章来自一个论坛里的回帖,哪个论坛记不得了!    在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和 ...

  5. Java中堆和栈的区别(转)

    栈与堆都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆.      Java的堆是一个运行时数据区,类的对象从中分配空间.这些对象通过new. ...

  6. C中堆和栈的区别

    C++中堆和栈的完全解析 内存分配方面: 堆: 操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删 除,并 ...

  7. jvm中堆和栈的区别

    1.前言.    其实jvm能优化的空间不多,最主要的是使用的共享内存不要超过默认的2g或者自己调的参数.但了解一下还是有点意思的,建议面试时还是要看,别学笔者裸奔. 2.区别.   网上说是有5点区 ...

  8. java中堆和栈的区别

    从宏观上来讲,栈内存:存储基本数据类型.堆内存:存储实际的对象内容.说明白点就是new出来的东西. int a = 3; int b = 3; a = 4; 编译器首先会处理int a = 3;将a进 ...

  9. java中堆与栈的区别

    堆与栈都是java中常用的存储结构,是内存中存放数据的地方. 堆:主要存放运行时创建(new)的对象.主要用于储存对象,存取速度慢,可以运行时动态分配内存,生命周期不需要提前确定. 栈:主要存放基础类 ...

随机推荐

  1. oracle热点表online rename

    对于在线的繁忙业务表的任何操作都可能带来意想不到的风险.一张业务表,对partition key进行升位,其步骤是: rename原表 新建临时表 交换分区到临时表 升位临时表的字段的长度 交换临时表 ...

  2. HDU-2527 Safe Or Unsafe

    http://acm.hdu.edu.cn/showproblem.php?pid=2527 建哈夫曼树,哈夫曼编码,求wpl值. Safe Or Unsafe Time Limit: 2000/10 ...

  3. Android线程计时器实现

    cocos2dx的计时器很好用,但当app进入后台,其计时器会pause掉,如果想要一个稳恒计时器就得自己去实现完成了,在Cocos2d-x for ios中我们可以利用NSTimer类并结合objc ...

  4. Bzoj 2453: 维护队列 && Bzoj 2120: 数颜色 分块,bitset

    2453: 维护队列 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 578  Solved: 247[Submit][Status][Discuss] ...

  5. Android SlidingMenu 滑出侧边栏

    最近有个项目需要使用侧边栏,而且希望是左右两侧都能够滑出侧边菜单,在网上查找实现方式时,发现大家用的最多的还是大神jfeinstein10的SlidingMenu库,地址https://github. ...

  6. wand(weak and)算法基本思路

    一般搜索的query比较短,但如果query比较长,如是一段文本,需要搜索相似的文本,这时候一般就需要wand算法,该算法在广告系统中有比较成熟的应该,主要是adsense场景,需要搜索一个页面内容的 ...

  7. patch与diff的恩怨

    一.概述 diff和patch是一对相辅相成的工具,在数学上来说,diff类似于对两个集合的差运算,patch类似于对两个集合的和运算.diff比较两个文件或文件集合的差异,并记录下来,生成一个dif ...

  8. 缺少HTML Doctype造成的样式问题

    很简单的一个登陆界面: 代码: <html> <head> <style type="text/css"> form span { displa ...

  9. MySQL 优化Limit分页

    很多时候.我们需要选择出从指定位置开始的指定行数.此时.limit笑了     对于limit的定义是:     limit x,y     表示从第x行开始选择y条记录          在业务需要 ...

  10. linux内核--内核内存管理

    如题目所示,为什么要称作“内核内存管理”,因为内核所需要的内存和用户态所需要的内存,这两者在管理上是不一样的. 这篇文章描述内核的内存管理,用户态的内存管理在以后的文章中讲述. 首先简单的说明一下下面 ...