`
灵雨飘零
  • 浏览: 34996 次
  • 性别: Icon_minigender_1
  • 来自: 唐山
文章分类
社区版块
存档分类
最新评论

堆、栈、值类型、引用类型分析总结 Part 1

 
阅读更多

在.NET中或许我们不用担心内存管理以及垃圾回收器(Garbage Collection GC)的问题,但是我们还是应该了解这些东东以便在必要的时候优化我们程序的性能。而且,如果对内存管理如何工作有所了解,那将有助于解释我们每个程序里的每个变量的运行规律。这篇文章主要内容是解释堆(Heap)和栈(Stack),各种变量以及这些变量到底是如何工作的。

.Net Framework 在执行代码时,有两个用来存储对象的地方,也就是堆和栈,用于帮助执行我们的代码。它们驻留在机器内存中,包含了所有我们需要实现的信息。

Stack VS Heap

栈多多少少用来负责跟踪你的代码里正在执行什么,或者说代码里的什么东东被called。而堆则或多或少用来跟踪我们的对象,或者说数据,大多数情况下都是数据啦——后头再详解。

把栈想象成堆砌起来由上到下的盒子。每次我们调用一个方法,就新加一个盒子到栈顶,我们用这种方法跟踪我们的程序在执行些什么。我们能用的,永远只是最顶上的那个盒子。当我们把最顶上这个盒子用掉了的时候,也就是方法执行完毕并返回的时候,我们就恶狠狠的把它扔掉!然后接着处理下一个盒子里的东东。而堆其实也是类似的东东,除了它的目的是用来保存信息(绝大多数情况下不是用来跟踪程序执行),因此堆里面的任何东西都不受限制的随便访问。堆就像是床上我们没空收拾的洗好的衣服一般,我们能很快的随便拿任何一件起来。而栈则跟壁橱里一堆装鞋子的盒子似的,我们得一个一个的从顶上取下来才能拿到下一双鞋子。

heapvsstack1

上图虽然并不是真正的内存中堆栈的样子,不过有助于我们理解它们的区别。

栈是“自我维护”的,意思是基本上是管理自个儿的(而不是别的地方的)内存。当顶部盒子不在使用,就扔之(就不是自家的雪了)!而堆呢,不太一样的是,必须得跟GC打交道——这东西用来保证堆是clean(没有过多垃圾内存)的。(木有人喜欢地上摆一堆脏衣服吧!臭死了!)。

What goes on the Stack and Heap?

当代码执行时,堆栈里头主要放置四种类型的东东:值类型,引用类型,指针(Pointers),以及指令(Instructions)。

值类型:

c#中,值类型继承自System.ValueType:

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

引用类型:

而引用类型则有:

class, interface, delegate, object, string

指针:

内存管理模型中的第三种东东是对一个类型的引用。这个引用通常就是指指针。我们不能直接使用指针,它们被CLR所管理。指针不同于引用类型。当我们说某某是引用类型时,实际上就意味着我们要通过指针去访问这个类型的值。一个指针占用内存中的一块空间,只想内存中另外一块空间。指针跟任何别的放在堆栈中的东西一样,是要占用物理空间的。它的值要么是null,要么就是内存地址。

heapvsstack2

指令:

在后续文章中再解释,稍安勿躁……

How is it decided what goes where? (Huh?)

okok,再啰嗦两句我们就可以正式摆弄我们的堆栈了。

这里有两条黄金规则:

  1. 引用类型总是保存在堆里头——够清楚了吧
  2. 值类型以及指针,总是保存在其被声明(Declared)的地方。稍微复杂一丁点儿,因为需要理解什么是“其被声明的地方”

栈,就像我们刚才提到的,负责跟踪单个线程(thread)运行到哪儿了。你不妨把其想象成thread的状态机,每个线程都有自个儿的栈。当我们的代码调某个方法时,线程开始执行JIT编译过并且保存在方法表(method table)中的指令集,同时,它把方法参数压入线程栈中。然后开始执行代码并访问方法里需要的、同时已经存在于线程栈顶部的变量。举个例子吧:

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

让我们看看栈里头都发生了什么,记住我们看到的只是栈顶的东西,下头早有无数别的东东在里面了哦!

当我们开始执行这个方法时,方法的参数被压栈(稍后我们讨论参数传递)。

Notice: 方法并不存在stack里头,图例只是为了演示概念

heapvsstack3

下一步,控制(线程执行这个方法)被交给AddFive方法在方法表中的指令集,如果这是第一次使用这个方法,JIT编译将被执行。

heapvsstack4

在方法的执行过程中,我们需要内存来保存 "result”,因此栈顶为其分配空间。

heapvsstack5

方法结束了,result被返回。

heapvsstack6

这时栈顶指针将会移到最初AddFive方法开始的内存地址,这样所有刚才分配的内存空间都被清理掉了,然后接着执行AddFive更下面的函数(图中为显示)。

heapvsstack7

在这个例子里,result变量被压栈。事实上,任何时候值变量在方法内部被声明,都会被压栈。

不过,值变量有时候也会存储在堆里头。看看黄金规则二,值类型总是保存在被声明的地方。那么,如果值类型在方法(method)外部声明,同时本身又存在于引用类型内部时,那么就会被保存在堆里头。

在来一个例子:

如果我们有MyInt Class(引用类型):

public class MyInt
{
    public int MyValue;
}

另外一个方法正在执行中:

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

跟前头一样,线程开始执行方法,参数被压栈:

heapvsstack8

这个时候开始好玩了:

因为MyInt是引用类型,因此保存在堆里头,通过栈里的指针去引用它。

heapvsstack9

AddFive执行完毕以后,我们开始清理栈顶:

heapvsstack10

这样我们就把MyInt对象当作孤儿留在了堆里头,因为栈中没有任何指针在引用它了!

heapvsstack11

这时候就是GC大显身手的时候啦!一旦我们程序达到某个特定的内存阀值(threshold)而我们需要更多堆空间时,GC就会开动。GC完全停止所有运行中的线程,找到所有堆里没有被引用的对象,然后像秋风扫落叶般的删除它们。GC重新组织所有滞留在内存中活动的对象,并调整堆栈中对其引用的地址。可以看到,从性能角度来说,这可真是很费时间。所以现在你是不是觉得注意一下堆栈的处理会有助于你写出高性能的代码呢?

OK,好了好了,很棒很棒,不过,这东西到底会如何来折磨俺们呢?

好问题。

当我们使用引用类型时,我们实际上是使用指向该对象的指针,而不是对象本身。当我们使用值类型时,我们使用的就是值本身。清楚还是不清楚?

最好还是来个例子吧。

如果我们执行以下方法:

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。

为什么?

在第一个例子里所有事都按照预定计划行事:

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

heapvsstack12

在后面的例子里,我们得不到3是因为x和y都指向堆里的同一个对象。

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

heapvsstack13

希望本文能帮助您更好的理解值类型和引用类型基本的区别,以及指针是什么,什么时候会用到指针。在后面的系列中,我们进一步的阐述内存管理,特别的会多聊聊方法参数的问题。

分享到:
评论

相关推荐

    arcgis空间分析.part1

    arcgis空间分析.part1

    Linux内核网络栈源代码情景分析part1

    对linux内核源码进行详细的分析

    Linux内核网络栈源代码情景分析.part5.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包最后一部分。

    官方OPC UA标准规范Part1-part14全部部分

    1. **Part 1: Architecture** - 这一部分定义了OPC UA的整体架构,包括其服务模型、信息模型和通信模型。它描述了OPC UA服务器和客户端如何通过服务接口交互,并解释了信息模型的概念,这是OPC UA的核心部分。 2. *...

    Linux内核情景分析.part1.rar

    Linux内核情景分析.part1.rarLinux内核情景分析.part1.rarLinux内核情景分析.part1.rarLinux内核情景分析.part1.rar

    Windows内核情景分析.part1.rar

    Windows内核情景分析.part1.rar

    C#编程语言详解 Part1

    ### C#编程语言详解 Part1 知识点总结 #### 一、简介 - **Hello World**: C#中创建一个简单的“Hello World”程序是非常直接的。这通常用于展示语言的基础语法,例如如何定义主方法(`Main`),以及如何使用控制台...

    Linux内核网络栈源代码情景分析.part4.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包的第4部分。

    Linux内核网络栈源代码情景分析.part2.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包的第二部分。

    Linux内核网络栈源代码情景分析.part3.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包的第3部分。

    接口协议栈总结

    ### 接口协议栈总结 #### 一、概述 本文档旨在总结接口协议栈的相关知识点,特别是Simple Control Transmission Protocol (SCTP) 和 MTP3 User Adapter (M3UA) 层的相关信息及其在网络通信中的作用。接口协议栈是...

    深入浅出Linux+TCP_IP协议栈 共三卷 part1

    深入浅出Linux+TCP_IP协议栈 共三卷 part1

    算法设计技巧与分析.part1

    算法设计技巧与分析.part1 算法设计技巧与分析.part1

    恶意代码分析实战.part2

    本书一方面从内容上更侧重于恶意代码分析技术与实践方法,而非各类恶意代码的原理技术与检测对抗方法;另一方面更加侧重实用性,能够引导读者们在实际恶意代码样本分析过程中使用书籍中所介绍的各种分析技术、工具和...

    PostgreSQL数据库内核分析.part1

    PostgreSQL数据库内核分析.part1 因为对上传内容有限制,所以分开传,跟part2放在同一文件夹下解压就能得到完整的pdf,亲测可用~

    系统分析师教程2008版part2

    软考系统分析教程,最新的系统分析师教程2008版,张友生著.pdf格式. 由于文件比较大,所以分成了7个小的rar文件. 分别是: 系统分析师教程2008版part1 系统分析师教程2008版part2 系统分析师教程2008版...

    Linux内核情景分析.part3.rar

    Linux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核...

    分布式系统常用技术及案例分析.part1

    分布式系统常用技术及案例分析 真正的完整版,一共三个包,一个包传不来有大小限制。分布式系统常用技术及案例分析.part1,分布式系统常用技术及案例分析.part2,分布式系统常用技术及案例分析.part3

    Linux_内核源代码情景分析.part1.rar

    Linux_内核源代码情景分析.part1 Linux_内核源代码情景分析.part1

Global site tag (gtag.js) - Google Analytics