Stack vs Heap

RiCo 技術農場
RiCosNote
Published in
6 min readMay 12, 2019

我身為.net developer,但很少真正處理記憶體分配和管理,.net framework記憶體管理和GC幫我們處理大部分工作和優化,但我覺得還是要了解這行為是如何工作的,因為我認為心法也是很重要的,另外,當有人質疑你寫的程式碼,造成application非常吃記憶體,這時你也才能和別人解釋,我這變數或instance是被分配在記憶體的那種類型(stack and heap),兇手不是我,同時,也請對方提供明確數據,證明她不是用猜。

我們寫的程式碼,會存放在.net framework兩種地方,分別是heap 和 stack,我先簡單解釋這兩種記憶體類型差異。

heap: 大致上負責追蹤我們的object,大部分是我們的 data,有點像保存訊息,很少會追蹤執行情況,任何data存放在heap,在stack中我們都能隨時存取,沒有任何限制,但這些資料在heap中需要被清理,不然可能會遇到OOM問題,換句話說,需要擔心GC的記憶體管理和回收處理,當我們需要更多heap size或達到記憶體臨界值,大致上GC就會自動起來進行記憶體清理工作,這時候會停止所有running threads,然後再heap中,找所有object(Managed memory)沒有被任何參考並且刪除該data,接下來重新組織所有object並釋放空間,且一併調整stack和heap相關pointer,到這裡你應該可以想像這一連串操作成本滿昂貴的,這也是為什麼,大家常在說不要頻繁觸發GC,因為對application效能有一定影響,沒什麼特殊需求的話,就讓.net自行處理,當然,有一些設定可以更改GC行為,例如:GC啟動時,不要強制停止所有runing threads,可以先讓GC等待1秒後,讓application先完成後,GC後續在處理,但相對GC整體處理時間也會被拉長,總之,這是一個取捨抉擇,沒有那一種行為一定就是王道,你覺得該Application很重要,那就讓GC多等一下。

stack: 大致上負責追蹤我們程式碼被什麼執行(或 called),所以我用try catch都會加入StackTrace 資訊,當發生exception,這樣我才可以知道程式碼整個執行過程,最後是掛在那一個method。每一條thread都有自己的stack,可以想像當我們呼叫一個method,一條thread狀態在starting,前提是JIT已經compiled並將該method放入method table(包含參數)中,但這裡我們還是可能會遇到StackOverflowException。

stack有點像鞋盒,必須先拿掉上層的盒子,才能存取下一層的盒子。基本上,自我管理記憶體。

看完這些文字或許你還是很模糊,下面我用程式並搭配圖片,我想你應該會更有感覺(需要一些些想像力)

void Main(){var portfolio= 1;var age = 38;var employee = new Employee{Portfolio= portfolio,Age = age};}

上圖我們可以知道,我們宣告的value type(because they are from System.ValueTyp,such as bool,char,int,byte,struct…),是一層一層堆上去,但reference type(because they are from System.Object, sucah as class,interface,object,string,delegate)會先在stack儲存pointer並指向heap memory,這時會依照object大小挖一塊記憶體來存放,當離開function後,stack會自動釋放記憶體,可是heap並不會自動釋放我們data所佔用記憶體,下面我們先來看下pointer。

pointer:我們雖然不用明確撰寫Pointer,因為CLR(Common Language Runtime)幫我們自動管理這些參考,所以當我們一個參考型別如何存取時,基本上都是透過pointer(都是memory address),當然pointer也會占用記憶體空間,但基本都是很微不足道的,pointer在stack和heap都會使用到。

下面用reference type method解說

public int GetProtfolio(){var rico=new Employee{Protfolio=10,Age=38};var sherry=new Employee{Protfolio=12,Age=18};sherry=rico;sherry.Protfolio=15;return rico.Protfolio;}

上面所返回的rico.Protfolio=15,如果不懂得需要自行了解value type 和reference type有啥不同。

上圖可以知道,為了節省heap size,透過pointer的memory address指向同一塊記憶體,因為reference type才有這特性,程式中new兩個instance,都有各自的pointer,但我又將rico指派給sherry,導致pointer的memory address都是相同的記憶體位置,雖然我們只set了sherry的protfolio屬性=15,但返回rico的portfoilo屬性是15,主因都是指向相同的記憶體位置。

另外,效能影響還有boxing 和unboxing,boxing簡單來說就是將value type轉換為reference type(V to R),unboxing就是將reference type轉換為value type(R to V),我們可在IL中明確看到。

public void SimpleAssignment(){var a = 100;var b = a;}public void BoxUnboxAsssignment(){var a = 100;object b = a;var c= (int)b;}

參考

Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing

Memory allocation: Stack vs Heap?

C# Heap(ing) Vs Stack(ing) In .NET — Part One

--

--

RiCo 技術農場
RiCosNote

分享工作上實戰經驗,如SQL Server,NetCore,C#,WEBApi,Kafka,Azure…等,Architect,Software Engineer, Technical Manger,Writer and Speaker