看到一篇讲堆和栈的文章,是我目前为止见到讲的最易懂,详细和深入的。我翻译成中文。以此总结。

原文=》C#Heap(ing) Vs Stack(ing) in .NET

在net framework框架下。即使我们并不需要关心内存管理和垃圾回收,但是为了提高我们的应用程序性能,我们仍然需要这么去做,仍然需要对gc和内存管理保持关注。所以,理解内存管理是如何工作的,可以帮助我们解释我们程序代码中变量的行为。在本文中,将讨论堆和栈,变量类型,和变量的工作行为。

当代码执行的时候,net framework在内存中有两个地方存储元素项。如果你从来没有关注过,那么,让我来介绍下堆和栈。当我们的代码运行的时候,堆和栈往往相伴左右。他们寄宿在机器的内存中。并且当我们需要运行程序获取某个结果的时候,他们能提供一个“现场”能提供产生这个结果所需要的所有信息。

堆和栈有何区别呢?

栈,他负责跟踪我们的代码的是如何执行(或者说是怎么样被一步步函数调用的),而堆,则负责跟踪我们所有的对象(我们的数据,基本上是大部分数据,后面再讲)

想 想一下,假想,栈就是一个个叠起来的盒子一样。每一次当我们进行一次函数调用的时候,就像在最顶端再叠放一个盒子,这样来跟踪我们的程序的走向。在栈上, 我们只能用最顶上的那个盒子,当我们用完最顶上的那个盒子以后,就像一个函数执行完毕。然后我们扔掉它。这个时候,我们就要使用前一个被扔掉的盒子下面的 那个盒子。而堆也是相似的,只不过他的目的仅仅是存放信息,而并没有跟踪这些信息执行,没有保持对信息状态的跟踪。在堆里面,什么东西可以访问,什么东西 不能访问,这是没有限制的。堆,就像我们洗干净扔床上的衣服一样。我们根本不需要及时的来花时间把他们收拾好。如果我们确实需要收拾,我们也可以迅速的来 吧他们收拾好。而栈呢,更像鞋柜里叠起来的一个个盒子。如果我们想要拿某个盒子,那么必须先拿掉它上面的那个,看下图,很好懂。

上面这个图,很好的呈现了我们的内存中发生着什么,或者我们内存中数据呈现的一种状态,这可以更好的帮我们理解堆和栈(Stack and heap)

栈是自我管理的,什么意思呢,栈只需要关注自己的内存管理,当最顶上的盒子不用了,ok,扔掉它、而堆,另一方面,需要来关注垃圾回收(GC).说白了就是如何来保持堆的干净。没有人愿意东西乱扔,那是会让人恶心的。

堆和栈中有什么?

当我们的代码在执行的时候,我们有四种类型可以往堆和栈中放。值类型,引用类型,指针类型,代码指令

值类型

在C#语言中,所有以以下类型声明的东西均是值类型。因为他们继承自System.ValueType

bool

byte

char

decimal

double

enum

float

int

long

sbyte

short

struct

uint

ulong

ushort

引用类型

所有以以下类型声明的东西都是引用类型,因为他们继承自System.Object.(当然只针对于是System.Object类型的对象)

class

interface

delegate

object

string

指针类型

第三种纳入内存管理的类型是引用。通常情况下,一个引用被视为一个指针。我们并没有显示明确的去使用指针,指针是由CLR使用管理的。引用(指针)跟 引用类型是有区别的。通常情况下,我们说一个东西是引用类型,那么意味着什么,意味着我们可以通过指针去访问他。指针是什么呢?指针说白了,就是内存中的 一块区域,他指向内存中的另一块内存空间(这种指向存放的是地址)。指针,就像我们向堆和栈放的东西占要占据空间一样,通常里面存放的值是内存地址或者null

指令

后文再做介绍。

如何决定分配那种内存?

两条黄金法则

  1. 引用类型始终分配在堆上。

  2. 值类型和指针类型始终跟踪声明他们的类型,这有点难以理解。需要对栈的工作原理有一些深入的理解

栈,正如之前提到的一样,负责跟踪每个线程的代码执行走向(函数的调用)。你可以想象成一个线程状态。每个线程都是都一个线程栈的。当我们的代码发生了一次方法调用。那么线程将开始执行JIT编译的代码。并且该方法会在方法表上登记,方法参数也会被压入线程栈。然后当我们进入方法,初始化变量后,他们已经被放置在栈顶了。下面这个例子可能更好理解。

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

下面描述下栈顶发生了什么。

记好,我们要寻找的东西,永远始终是在栈顶。(即使栈里有其他东西,我们需要的东西应该始终在栈顶。)

当我们开始执行方法的时候。方法的参数会被放入堆栈。后期会来介绍参数传递。

注意,方法是不会存活在堆栈上的。恩,如图说明

接下来。控制(线程执行)会转移到方法表中登记的AddFive()方法上,如果方法是第一次调用。即时编译器会被调用,执行IL代码的编译工作

当方法执行的时候,我们需要一些内存空间来存放方法执行的结果。存放结果的内存空间是会分配在栈上。

当方法执行完毕,我们的结果返回

当指针从新指向addFile的调用处时候(地址必须可访问)。这个时候,所有在栈上分配的内存空间都会被回收,我们将回到栈中的前一个方法。方法调用返回。

在这个例子中,我们的结果变量是分配在栈中的。而实际上,在方法体内声明的所有值类型,他们的内存空间分配均是分配在栈上。

现在,值类型有时候也是可以分配在堆中的,记住,值类型的具体分配,总是取决于声明它的类型。

如果一个值类型,他在方法体外声明,但是在一个引用类型内部声明。它会跟随声明它的引用类型一样,分配在堆上,跟寄生一样。

看另外一个例子

有一个引用类型

public class MyInt

{

public int MyValue;

}

执行以下方法

public MyInt AddFive(int pValue)

{

MyInt result = new MyInt();

result.MyValue = pValue + 5;

return result;

}

跟之前说的一样。线程开始执行方法,参数都会分配在线程栈上。

现在问题是,方法执行过程中,什么时候需要特别注意??

因为MyInt是一个引用类型。他会被分配在堆上。并且会被栈上的一个指针引用(栈上的指针指向堆中的这个对象)

AddFive()方法结束以后,我们要开始进行垃圾回收了。

这个时候MyInt对象在堆会成为一个孤儿对象。什么意思。栈中没有任何指针指向MyInt对象。无任何引用、

接下来,GC(垃圾回收器)要开始派上用场了

是这样的。一旦内存达到一个极限,这个时候我们需要更多的堆空间。那么GC就启动了。GC会强制停掉所有正在运行的线程。从堆中找出所有不再被程序访问对象。然后,删除他们。这个时候,GC会识别堆中所有对象。并且调整栈和堆中指向他们的指针。跟你想象的一样。这种工作是非常消耗性能的。所以当我们尝试写一些高性能的代码的时候,我们迫切的需要知道堆和栈中有那些东西。

好了,非常好,但是它对我有何影响呢?

好问题

当我们使用引用类型的时候,我们实际上是在使用指针处理该类型。处理的不是类型本身。而是指针所指的(对象)。这很好理解

也用一个例子来说明

我们执行以下代码

public int ReturnValue()

{

int x = new int();

x = 3;

int y = new int();

y = x;

y = 4;

return x;

}

我们得到的结果是3,是不是很简单。

如果我们用我们之前定义的MyInt

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;              
                returnx.MyValue;
          }

得到的结果是4

为毛? x.MyValue 为什么会是4... 来看下我们坐了什么,看是否讲得通。

在第一个例子中,代码如预期般的执行

public int ReturnValue()

{

int x = 3;

int y = x;

y = 4;

return x;

}

在下一个例子中我们得到的结果是4,因为变量x和y都指向相同的对象。

public int ReturnValue2()

{

MyInt x;

x.MyValue = 3;

MyInt y;

y = x;

y.MyValue = 4;

return x.MyValue;

}

希望这能让你更好的理解C#中的值类型和引用类型。并且对指针和什么时候用指针有一个基本的了解。

在下一节我将更加深入的讲解内存管理,并且将会着重讲解方法参数传递、

C#中的堆和栈的更多相关文章

  1. (十一)C语言中内存堆和栈的区别

    在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...

  2. C语言中的堆与栈20160604

    首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!一.前言介绍:C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件是静态区域由代码段和数据段(由二部分部分组成:只读数据 段,未初始化数 ...

  3. Java中的堆和栈的区别

    当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更糟糕的是,Java中存在栈这样一个后进先 ...

  4. java中的堆、栈、常量池

    java中的堆.栈.常量池 分类: java2010-01-15 03:03 4248人阅读 评论(5) 收藏 举报 javastring编译器jvm存储equals Java内存分配: 1. 寄存器 ...

  5. (转)认识java中的堆和栈

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

  6. java中的堆与栈

    Java 中的堆和栈 Java把内存划分成两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配 . 当在一段代码块定义一个变量时,Java就在栈中 ...

  7. 详细介绍Java中的堆、栈和常量池

    下面主要介绍JAVA中的堆.栈和常量池: 1.寄存器 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈 存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在 ...

  8. Java中的堆和栈

    Java中的堆和栈 栈内存 存放基本数据类型和引用变量 堆内存 存放运行时创建的对象 一般来说,通过new关键字创建出来的对象都放在堆内存中 由于JVM是基于堆栈的虚拟机,而每个Java程序都运行在一 ...

  9. 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型

    小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...

  10. 在JS中关于堆与栈的认识function abc(a){ a=100; } function abc2(arr){ arr[0]=0; }

    平常我们的印象中堆与栈就是两种数据结构,栈就是先进后出:堆就是先进先出.下面我就常见的例子做分析: main.cpp int a = 0; 全局初始化区 char *p1; 全局未初始化区 main( ...

随机推荐

  1. Android:文件夹显示红色叹号

    有感叹号,说明有的文件损坏或丢失了 解决方法: 右击工程,Build Path..->Configure Build Path...->Java Build Path 可以看到引用的jar ...

  2. 254. Factor Combinations

    题目: Numbers can be regarded as product of its factors. For example, 8 = 2 x 2 x 2; = 2 x 4. Write a ...

  3. NSSize

    #import <Foundation/Foundation.h>   int main(int argc, const char * argv[]) {    @autoreleasep ...

  4. POJ 1808 Quadratic Residues(平方剩余相关)

    题目链接:http://poj.org/problem?id=1808 题意:如下.对于素数p,若存在x使得x^2%p=a,则其值为1.否则为-1.现在给出a.p,计算其值. 思路: 若a为正数则利用 ...

  5. libmysqlclient.so.15: cannot open shared object file: No such file or directory

    错误: ./mafsInRegion: error while loading shared libraries: libmysqlclient.so.15: cannot open shared o ...

  6. UVa 10253 (组合数 递推) Series-Parallel Networks

    <训练之南>上的例题难度真心不小,勉强能看懂解析,其思路实在是意想不到. 题目虽然说得千奇百怪,但最终还是要转化成我们熟悉的东西. 经过书上的神分析,最终将所求变为: 共n个叶子,每个非叶 ...

  7. 运维角度浅谈MySQL数据库优化(转)

    一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善.这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分 ...

  8. php复制目录及文件

    <?php /* 复制目录 */ function copydir($dirsrc,$dirto){ if(is_file($dirto)){ echo "目标不是目录不能创建&quo ...

  9. MYSQL的分区字段,必须包含在主键字段内

    MYSQL的分区字段,必须包含在主键字段内   MYSQL的分区字段,必须包含在主键字段内 在对表进行分区时,如果分区字段没有包含在主键字段内,如表A的主键为ID,分区字段为createtime ,按 ...

  10. ByteBuffer用法小结

    在NIO中,数据的读写操作始终是与缓冲区相关联的.读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区.缓冲区是定长的,基本上它只是一个列表,它的所有元素 ...