开发者社区> 杰克.陈> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

译文---C#堆VS栈(Part One)

简介: 原文:译文---C#堆VS栈(Part One) 前言   本文主要是讲解C#语言在内存中堆、栈的使用情况,使读者能更好的理解值类型、引用类型以及线程栈、托管堆。       首先感谢原文作者:Matthew Cochran 为我们带来了一篇非常好的文章,并配以大量图示,帮助我们更好的理解堆栈之间的调用,本文是在作者原文的基础上进行内容上的精简以及加入我个人在这方面的理解和注释。
+关注继续查看
原文:译文---C#堆VS栈(Part One)

前言

  本文主要是讲解C#语言在内存中堆、栈的使用情况,使读者能更好的理解值类型、引用类型以及线程栈、托管堆。

      首先感谢原文作者:Matthew Cochran 为我们带来了一篇非常好的文章,并配以大量图示,帮助我们更好的理解堆栈之间的调用,本文是在作者原文的基础上进行内容上的精简以及加入我个人在这方面的理解和注释。

      最后要感谢博客园的田志良,当我搜索堆栈内部使用时,搜索到了作者的文章,吸取了大量有用的知识,而且翻译的也非常好。唯一美中不足的可能是仅仅翻译了Matthew Cochran这个系列文章的第一篇,而忽略了后面的几篇,导致内容上略有不完整,所以,我会在继续完成后续的工作,为大家答疑解惑。

 

下面引用作者写作此文的原因:

 虽然有了.Net Framework我们不用担心内存管理和垃圾回收问题,但是我们仍然要记住内存管理和垃圾回收为了优化程序。而且,有一个内存管理如何运行的基本概念将帮助我们解释我们写的每一个程序中变量的行为。

         注:限于本人英文理解能力,以及技术经验,文中如有错误之处,还请各位不吝指出。

目录

C#堆栈对比(Part One)

C#堆栈对比(Part Two)

C#堆栈对比(Part Three)

C#堆栈对比(Part Four)

 

栈vs堆:不同之处

  栈负责追踪那些在我们代码中执行的内容(或者是那些被调用的内容)。而堆则负责追踪我们的对象(我们的数据,当然大多数情况下都是“数据”,稍后我会讨论这个问题)。

  注:栈类似于代码执行过程的一个容器,而堆则类似于保存数据的容器。

  把栈想象成一个一系列的盒子,一个落着一个在上面。当我们每次调用一个方法(called a Frame)时,我们通过将盒子叠加在最顶部的盒子上来观察在我们的代码中究竟发生了什么。其实,我们只能使用栈中最顶部的盒子。当我们处理完最顶部的盒子(我们执行过的方法、函数)后我们就丢弃它并且继续使用之前在顶部的盒子。堆对于栈很相似,只是堆的目的是保存信息(大多数情况下不追踪执行代码),所以在任何时刻我们的堆都能被访问。有了堆,我们将不像栈一样有那么多访问约束。堆更像是一堆在床上我们还没来得及整理的洗干净的衣服;我们能快速的得到我们想要的衣服。而栈更像是在壁橱里的一摞鞋盒,我们拿掉最顶上的鞋盒为了得到下面的盒子里的鞋。

  注:网上找了两个图片替代作者的图片,这样会更生动些。左侧为栈,右侧为堆。

  栈可自我维护,这意味着它基本只关系它自己的内存管理。当栈顶的盒子不再使用之后,随即就丢弃掉。堆,在另一方面而言,必须关心垃圾回收问题,这些问题主要是处理如何保持堆整洁(没有人喜欢乱堆脏衣服,臭气熏天~~~)。

  注:栈中的内容是每执行一次指令之后即释放掉,所以无需关注资源泄漏;堆则需要GC不定时的回收已不再使用的资源,需维护并关注性能问题。

堆栈上究竟发生了什么

     在我们的堆或者栈中我们有四个主要类型:值类型、引用类型、指针类型和指令。

  1. 值类型:

  在C#中,所有的值类型均继承自System.ValueType这个抽象类

  注:System.ValueType继承自System.Object,并且重写了.ToString()等方法,以便阻止某些情况下的装箱问题。

  • bool
  • byte
  • char
  • decimal
  • double
  • enum
  • float
  • int
  • long
  • sbyte
  • short
  • struct
  • uint
  • ulong
  • ushort

  2. 引用类型:

  在C#中,如下的引用类型均继承自System.Object,当然除了Object其自身。

  • class
  • interface
  • delegate
  • object
  • string

  3. 指针类型:

  放置在我们内存管理中的第三个类型是一个引用类型,这就是我们常说的指针。我们不明确的使用指针,他们是被CLR管理的资源。指针(或者引用)是不同于引用类型的,当我们在讨论引用类型的时候,就是说我们是通过指针使用引用类型的。一个指针是指向另一个内存空间的一大块内存。一个指针占据空间就像是一个我们放置在堆或者栈中,并且它的值是一个地址或者为Null。

  4. 指令类型:

  稍后,在后面的文章中我们会分析。

 

如何推断类型是在堆上,还是在栈上?

  这里,我们有两条黄金定律:

  1. 引用类型总是在堆上创建,十分简单,是吧?
  2. 值类型和指针类型总是在它声明的地方创建。这有点复杂并且需要懂一点栈是如何工作的。

 

  栈,正如我们前面讲的,是负责追踪每一个线程中代码执行情况(或者被调用)。你可以认为它是一个线程状态并且每一个线程有它自己的状态。当我们的代码调用执行一个方法,开始执行一个已经被JIT编译过的指令,并且存活在方法表中(live on the method table),它也将参数放置在线程栈中。然后,当我们进入方法体并且带着参数执行方法时,指令将被提到栈顶部。

下面我们用一段代码来演示:

1 public int AddFive(int pValue)
2 {
3         int result;
4         result = pValue + 5;
5         return result;
6 }

  这就是发生在栈上的事情。必须记住的是我们正在观察的是已经存在于栈上的:我们执行方法并且方法参数被放置在栈中,稍后我们谈论参数细节。

此图中的AddFive方法并不存在于栈中,这里只是为了演示说明。

  下一步,命令执行到存在于我们的类型表中的AddFive()方法,如果第一次执行该方法,JIT会执行一次。

  当方法执行时,我们需要一些内存为“result”这个变量,并且这个变量将在栈上创建,如下图:

 

  方法执行完毕,返回结果。如下图:

  所有在栈上创建的内存将被清理,通过将指针指向一开始AddFive()指向的可用内存地址。

  在这个例子中,我们的“result”变量将被放置在栈中。事实上,每次当值类型带着方法体被声明时,它将被放置在栈中。

  现在,值类型有时也被放置在堆中。请记住这个规则,值类型是根据其声明的地方而决定其是在堆还是在栈上的。如果一个值类型在方法体外面声明的,但是在引用类型内部,这样它将被包裹在引用类型,并且在堆上创建。

  例子如下:

  如果我们有如下的MyInt类(类自然是一个引用类型)

public class MyInt
{          
             public int MyValue;
}
执行如下:
public MyInt AddFive(int pValue)
{
             MyInt result = new MyInt();
             result.MyValue = pValue + 5;
             return result;
}

  就像刚才一样,线程开始执在线程栈上的行方法和参数,如下图:

  现在就比较有趣了。因为MyInt是一个引用类型,MyInt类型将被放置在堆中,并且被一个放置在栈中的指针所引用(指向),如下图:

  在AddFive()被执行后,我们将清理栈,如下图:

  我们只剩下一个孤独的对象在堆中(在栈中将没有任何指针指向堆中的MyInt),如下图:

  这就是GC展现实力的舞台。当我们达到一定内存瓶颈时我们需要堆中要有更多的空间,这时GC出场。GC将停止所有运行中的线程(完全停止),找出在堆中所有没有被引用的对象并且删除它们。GC将重新组织所有在堆中的对象以获得空间,调整所有在堆以及栈中的指针。就像你想象的那样,这将花费十分昂贵的性能,所以现在你就能看出当你在写高性能代码时,关注堆栈中有什么是如此的重要。

  注:1.GC回收一般发生在程序内存不够用时,否则不会发生除非手动调用。2.手动调用GC可实现强制“尝试”回收资源。3.GC中的所有资源是分“代”的,每次检测堆中的对象是否还有引用,如果有当前的“代”数加一,否则减一,GC回收“代”数最小的资源,这也就解释了为什么即使我手动调用GC.Collect()方法之后,对象还是没有马上被回收的问题。4.频繁调用GC.Collect()会导致频繁的线程中断,从而严重影响性能。

  好的,十分棒,跟我有什么关系?

  问的好。

  当我们用引用类型时,我们正在处理指针这个类型,而不是引用(实际的方法、类型)其本身;当我们用值类型时,我们就是用的类型自身。这令人费解,对吗?

  再来,下面这个例子很好的诠释了这个问题:

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

  最终的结果是3. 十分简单,不是吗?

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!

  为什么?…x.MyValue是如何变为4的?看一看我们正在做的是什么并且是否这样做有意义:

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

  注:值类型是传递值,而非传递引用,如下图:

  下一个例子,我们没得到“3”,因为x和y都是指向同一个堆对象的变量。

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

  注:实际来讲,引用类型则是指向堆中的同一个对象。

  希望这能让您对值类型和引用类型有一个更好的理解通过C#代码并且理解指针的用法和在那里使用。

在下一部分(Part Two),我们将更深入的聊一聊内存管理,尤其要是讨论方法参数。

 

总结

  1. 堆与栈的概念及不同点:在内存中栈主要负责处理线程中的命令,并且是以栈Stack的形式读取与执行的;堆主要是存储方法体以及数据,类似于床上散落的衣服,可供随机读取。
  2. 值类型与引用类型不同点:引用类型永远存在于托管堆上,值类型在哪取决于声明的位置。
  3. 堆和栈上的垃圾回收:栈有自我维护特性,执行完语句马上释放不会造成资源泄漏。堆则需GC回收,并且符合GC回收的规则,很多堆上的内容在程序退出前都没有被回收,很可能是无意中某处还保留着内容的引用导致,这将严重影响性能。
  4. 值类型与引用类型在改变内容时处理的方式不同:值类型执行内容拷贝,引用类型始终更改的是所引用的内容,这将导致两者行为上的不一致。

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
最小堆最大堆了解吗?一文了解堆在前端中的应用(一)
在下面的这篇文章中,将讲解堆的基础知识,并手动地用 js 来构建一个最小堆,同时剖析几道经典的 leetcode 算法题。
66 0
手把手教你安装WordPress详细教程(图文)
如果还有不了解宝塔面板怎么使用的小伙伴,可以看下我总结的系列教程,保证从新手变老鸟:
384 0
java中文乱码解决之道(三)—–编码详情:伟大的创想—Unicode编码
随着计算机的发展、普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号。为了解决这种不兼容的问题,伟大的创想Unicode编码应时而生!!
3301 0
UART
一、S3C2410内置的UART控制器S3C2410内部具有3个独立的UART控制器,每个控制器都可以工作在Interrupt(中断)模式或DMA(直接内存访问)模式,也就是说UART控制器可以在CPU与UART控制器传送数据的时候产生中断或DMA请求。
1086 0
WordPress中文文档
1.关于WordPress 2.5版本 2.6版本 2.7版本 2.7主机兼容性 2.7主题兼容性 2.7插件兼容性 2.8版本 Automattic产品和服务 Codex帮助文件 Codex样式 Codex的CSS样式 Codex社区门户 Codex维护 Co...
3519 0
MATLAB概率统计函数(2)
4.5  随机变量的数字特征 4.5.1  平均值、中值 命令  利用mean求算术平均值 格式  mean(X)       %X为向量,返回X中各元素的平均值 mean(A)       %A为矩阵,返回A中各列元素的平均值构成的向量 mean(A,dim)   %在给出的维数内的平均值 说明  X为向量时,算术平均值的数学含义是,即样本均值。
1007 0
java连接Access数据库的代码
闲来无事,整理了一下java代码。算做个复习笔记了。   import java.sql.*;public class dbaccess{ public static void main(String args[]) throws Exception{  Class.
1092 0
+关注
杰克.陈
一个安静的程序猿~
文章
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载