代码改变世界

.net 垃圾回收学习[C#中的stack和Heap]

2011-08-20 18:27  一一九九  阅读(819)  评论(0编辑  收藏  举报

From: http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx

翻译加学习。

         虽然在.net framework下我们不需要主动的关心内存管理和垃圾回收,但是我们仍然需要了解内存管理和垃圾回收以便更好的优化我们的application. 当然,对内存管理如何工作有关基本的了解有助于我们理解我们写的程序中变量的行为。

       .net framework使用stack和heap两种结构在内存中存储指令和数据。

Stack vs Heap: what’s the difference?

         Stack或多或少的负责记录正在我们代码中执行的指令和数据, heap或多或少的负责记录我们的Object的一些数据。

           可以把Stack想象为一个在另外一个的上面堆起来的盒子,当我们调用一个方法的时候,我们把这个方法(called a frame)象盒子一样堆积在已经调用的方法之上,通过这种方式我们跟踪了我们Application中代码的执行情况。我们只能使用在Stack上面的盒子,当我们执行完毕正在运行的代码的时候,我们把它从Stack中扔到一边,然后进入Stack上的最上面的代码继续执行。 heap的情况类似,除了他的主要目的是为了保存数据, 没有像Stack一样的访问限制,可以按照任意顺序访问。 形象示意如下:

heapvsstack1

Stack是自我维护的,意味着基本上不需要我们手动进行内存管理,它有自己的内存管理体系(不管是OS来分配还是释放), 而heap则是我们需要关系,和垃圾收集存在一定关系的。

What goes on the stack and heap?

          当代码执行的时候我们有四种类型的东西是要放在Stack和Heap上的,他们是: value types, reference types, pointers, and  Instructions.

  1. Value Types
          在C#中,下列的内容都属于Value Type,这些类型继承自System.ValueType, 除此之外都不算Value Types.
          Bool,   byte ,  char, decimal, double, enu, float, int, long, sbyte, short, struct, uint, ulong, ushort
  2. Reference Types
       
    使用下列类型声明的对象都是Reference Type,包括所有的从System.Object继承下来的类型(System.ValueType除外)或者属于下列类型的。
           class, interface, delegate, object,string.
  3. Pointers
         在内存管理中涉及的第三个数据类型是引用。引用经常被当做指针,我们并不会明确的使用指针,指针是被CLR管理的。 一个指针或者引用是和引用类型不相同的,一个指针是内存中一段内存块,保存着指向内存中的另一个地址。引用和我们放在Stack以及Heap中的其他东西一样占用空间,他的值为一段内存地址或者NULL。如下图所示。
    heapvsstack2
        
  4. Instructions
    一会再说。。。

How is it decided what goes where ?

    有两条规则:

  • 引用类型总是在Heap上。
  • Value Typ和Pointer是在他们被声明的地方上。

        stack负责记录追踪我们执行代码中的线程情况,当我们调用一个方法的时候,执行线程开始执行JIT编译过的方法表上的指令,同时将方法的参数放到线程的Stack上,然后当我们采用这些变量进入方法的时候,他们就被放到了Stack的顶部。

       假如执行如下代码:
     

           public int AddFive(int pValue)
          {
                int result;
                result = pValue + 5;
                return result;
          }
 
接下来是在Stack的顶部会发生什么。记住我们要查看的是早已经放到Stack顶部的其他的东西。 一旦我们开始执行方法,方法的参数就会被放到Stack上。
注意:方法并不在Stack上,这里仅仅是一个引用。如下图:
heapvsstack3
然后,在我们类型的方法表中的AddFive方法的指令被传递给执行此方法的线程,假如这是我们第一次执行此方法,JIT编译器会编译此方法。
heapvsstack4
 
当方法执行的时候,我们需要为“Result”变量分配内存,此内存是分配在Stack上的。
heapvsstack5

方法执行完毕后,Result被返回。

heapvsstack6

然后在Stack上分配的内存被清理掉,Stack的指针被移到AddFive方法开始的地方,然后回到了Stack之前的方法上。

heapvsstack7

在这个示例中,“Result”变量是放在Stack上的。事实上,任何一个在方法体中声明的Value Type都会被放到Stack上。

现在,Value Types 有时候会被放到Heap上。 记住这条规则: Value Type always go where they were declared, Well, 假如一个值类型是在方法的外部声明的,但是在一个引用类型内部,那么它将会被放到heap上。

下面是另外一个例子:

假如我们有如下一个类:

public class MyInt
{
    public int Myvalue;
}

然后下面的方法将被执行:

public MyInt AddFive(int pValue)
{
    MyInt result  = new MyInt();
    result.MyValue = pValue + 5;
   return result;
}
像上次一样,线程开始执行方法,而且方法的参数已经被放到了线程的Stack上了,如下图:
heapvsstack8 
然后事情开始变得有意思了。。
因为MyInt是一个引用类型,它被在Heap上创建,在Stack上通过一个Pointer来引用。
heapvsstack9 
当addFive方法执行完毕后,我们开始清理。。。
heapvsstack10 
此时我们只剩下heap上的孤零零的MyInt对象,此时在Stack上没有任何引用指向MyInt.
heapvsstack11 
这时候也就是GC发生作用的时候。一旦我们的程序到达了某个内存点,而且我们
需要更多的heap空间,GC就会出来工作。GC将会停止所有的运行线程,找到
heap上所有的不在被main程序引用的对象,并且删除他们。GC将会重新组织
在heap上剩余的对象,然后在Stack和Heap上调整这些对象的引用。这个过程
是十分耗费性能的,所以你能意识到当需要些高性能的程序的时候为什么需要注意在Stack和Heap上有什么。
 
然后,很好,可是那个和我有啥关系?
当我们使用引用类型的时候,我们其实是通过指针和引用类型打交道,而不是那个对象自己。当我们使用值类型的时候,
我们是使用的对象本身,是不是?
 
如下例子所述:
假如我们执行下述方法:
public int ReturnValue()
{ 
        int x = new  int();
        x = 3;
        int y = new int();
        y = x;
        y = 4;
        return x;
}
 
我们将会得到值3,足够简单吧。
然而,假如我们使用之前定义的MyIntClass。

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;
}

heapvsstack12

在下一个例子中,我们没有得到3,因为x,y指向了heap上的同一个对象。

heapvsstack13

通过这些,你能够理解ValueType和ReferenceType的区别了吧。