天天看點

JVM中的Stack和Heap--------堆與棧

剛開始學習程式設計時,也常聽說堆棧,總以為它是一個東西。無意中看到堆和棧是不同的,但一直沒有時間研讀,今天看到一篇不錯講的比較清晰的文章,來分享一下。

————原文出處:http://developer.51cto.com/art/201003/188753.htm

在JVM中,記憶體分為兩個部分,Stack(棧)和Heap(堆),這裡,我們從JVM的記憶體管理原理的角度來認識Stack和Heap,并通過這些原理認清Java中靜态方法和靜态屬性的問題。

一般,JVM的記憶體分為兩部分:Stack和Heap。

Stack(棧)是JVM的記憶體指令區。Stack管理很簡單,push一定長度位元組的資料或者指令,Stack指針壓棧相應的位元組位移;pop一定位元組長度資料或者指令,Stack指針彈棧。Stack的速度很快,管理很簡單,并且每次操作的資料或者指令位元組長度是已知的。是以Java 基本資料類型,Java 指令代碼,常量都儲存在Stack中。

Heap(堆)是JVM的記憶體資料區。Heap 的管理很複雜,每次配置設定不定長的記憶體空間,專門用來儲存對象的執行個體。在Heap 中配置設定一定的記憶體來儲存對象執行個體,實際上也隻是儲存對象執行個體的屬性值,屬性的類型和對象本身的類型标記等,并不儲存對象的方法(方法是指令,儲存在Stack中),在Heap 中配置設定一定的記憶體儲存對象執行個體和對象的序列化比較類似。而對象執行個體在Heap 中配置設定好以後,需要在Stack中儲存一個4位元組的Heap 記憶體位址,用來定位該對象執行個體在Heap 中的位置,便于找到該對象執行個體。

由于Stack的記憶體管理是順序配置設定的,而且定長,不存在記憶體回收問題;而Heap 則是随機配置設定記憶體,不定長度,存在記憶體配置設定和回收的問題;是以在JVM中另有一個GC程序,定期掃描Heap ,它根據Stack中儲存的4位元組對象位址掃描Heap ,定位Heap 中這些對象,進行一些優化(例如合并空閑記憶體塊什麼的),并且假設Heap 中沒有掃描到的區域都是空閑的,統統refresh(實際上是把Stack中丢失了對象位址的無用對象清除了),這就是垃圾收集的過程;關于垃圾收集的更深入講解請參考51CTO之前的文章《JVM記憶體模型及垃圾收集政策解析》。

JVM中的Stack和Heap--------堆與棧

JVM的體系結構

我們首先要搞清楚的是什麼是資料以及什麼是指令。然後要搞清楚對象的方法和對象的屬性分别儲存在哪裡。

1)方法本身是指令的操作碼部分,儲存在Stack中;

2)方法内部變量作為指令的操作數部分,跟在指令的操作碼之後,儲存在Stack中(實際上是簡單類型儲存在Stack中,對象類型在Stack中儲存位址,在Heap 中儲存值);上述的指令操作碼和指令操作數構成了完整的Java 指令。

3)對象執行個體包括其屬性值作為資料,儲存在資料區Heap 中。

非靜态的對象屬性作為對象執行個體的一部分儲存在Heap 中,而對象執行個體必須通過Stack中儲存的位址指針才能通路到。是以能否通路到對象執行個體以及它的非靜态屬性值完全取決于能否獲得對象執行個體在Stack中的位址指針。

非靜态方法和靜态方法的差別:

非靜态方法有一個和靜态方法很重大的不同:非靜态方法有一個隐含的傳入參數,該參數是JVM給它的,和我們怎麼寫代碼無關,這個隐含的參數就是對象執行個體在Stack中的位址指針。是以非靜态方法(在Stack中的指令代碼)總是可以找到自己的專用資料(在Heap 中的對象屬性值)。當然非靜态方法也必須獲得該隐含參數,是以非靜态方法在調用前,必須先new一個對象執行個體,獲得Stack中的位址指針,否則JVM将無法将隐含參數傳給非靜态方法。

靜态方法無此隐含參數,是以也不需要new對象,隻要class檔案被ClassLoader load進入JVM的Stack,該靜态方法即可被調用。當然此時靜态方法是存取不到Heap 中的對象屬性的。

總結一下該過程:當一個class檔案被ClassLoader load進入JVM後,方法指令儲存在Stack中,此時Heap 區沒有資料。然後程式技術器開始執行指令,如果是靜态方法,直接依次執行指令代碼,當然此時指令代碼是不能通路Heap 資料區的;如果是非靜态方法,由于隐含參數沒有值,會報錯。是以在非靜态方法執行前,要先new對象,在Heap 中配置設定資料,并把Stack中的位址指針交給非靜态方法,這樣程式技術器依次執行指令,而指令代碼此時能夠通路到Heap 資料區了。

靜态屬性和動态屬性:

前面提到對象執行個體以及動态屬性都是儲存在Heap 中的,而Heap 必須通過Stack中的位址指針才能夠被指令(類的方法)通路到。是以可以推斷出:靜态屬性是儲存在Stack中的,而不同于動态屬性儲存在Heap 中。正因為都是在Stack中,而Stack中指令和資料都是定長的,是以很容易算出偏移量,也是以不管什麼指令(類的方法),都可以通路到類的靜态屬性。也正因為靜态屬性被儲存在Stack中,是以具有了全局屬性。

在JVM中,靜态屬性儲存在Stack指令記憶體區,動态屬性儲存在Heap資料記憶體區。