net framework中的內存分配

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上了,如下圖:
 
然後事情開始變得有意思了。。
因爲MyInt是一個引用類型,它被在Heap上創建,在Stack上通過一個Pointer來引用。
 
當addFive方法執行完畢後,我們開始清理。。。
 
此時我們只剩下heap上的孤零零的MyInt對象,此時在Stack上沒有任何引用指向MyInt.
 
這時候也就是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的區別了吧。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章