一直以來都在接受一個論調:在Java的內存管理中,基礎類型變量保存在棧中,對象類型保存在堆中。 這實際上是一個很粗的論調,會引發不少錯誤的理解。java
這篇博客的產出,僅是由於腦子裏偶然間冒出來的一個問題:java棧內存的內存回收是怎麼作的? 由於這個問題,引起了一系列的小問題:全部的基礎類型變量都保存在棧中嗎?棧和堆上都在運行着咱們熟悉的GC嗎?怎麼都感受GC算法在棧這種結構上無法跑啊……堆內存,棧內存到底是什麼樣的?程序員
這裏我很是推薦你閱讀一本關於java虛擬機的書:《深刻理解java虛擬機》 -- 周志明算法
Java棧內存的內存回收是怎麼作的?彷佛由於這個問題過小白了,大部分關於JAVA GC的博客都不會特別的指明,GC是運行在JAVA內存堆上的。但也是由於這個緣由,對於一些基礎知識不太好的小白(好比我),看多了這種博客以後,想固然的認爲了GC負責了整個java內存的管理,好像~棧內存也是由GC進行回收的……(畢竟棧的全名還叫堆棧呢)bash
當有一天我翅膀硬了點,忽然醒悟,棧這種只能在一頭進出的數據結構,跑的是什麼GC算法???如此簡單的數據結構,也不須要GC了吧?數據結構
終於在查閱了不少資料後,確認了一個問題:GC運行在JAVA堆上,負責java堆內存的回收。優化
那麼確認這個大前提下,後面連續不斷的問題就都冒出來了:棧內存是怎麼進行內存回收的?全部的基礎類型變量都保存在棧內存中嗎?spa
棧嘛,僅容許在線性表的一端進行插入和刪除運算,因此分配內存就在棧頂插進去一塊,回收,就從棧頂取出來。線程
這種管理結構,從方法的角度其實很容易理解,咱們看下面一段僞代碼:code
public void main(){
method1();
}
public void method1(){
...
method2();
}
public void method2(){
...
method3();
}
public void method3(){
...
//end
}
複製代碼
代碼很簡單,main方法調用了method1,method2調用了method3,method3結束了。用圖的形式更直觀:cdn
從main方法開始執行,各方法依次入棧,每一個方法執行的同時,都會建立一個棧幀,而後進入虛擬機棧。
method3方法執行結束後,各方法(棧幀)依次出棧:
這就是java棧內存,用《深刻理解java虛擬機》一段話來描述:
java虛擬機棧也是線程私有的,他的生命週期與線程相同。虛擬機棧描述的是java方法執行的內存模式:每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表,操做數棧,動態連接,方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。
OK,到這裏咱們基本上能夠解決第二個問題:java棧內存的內存回收就是利用方法和棧的特性來作的!
那麼第三個問題:全部的基礎類型變量都保存在棧中嗎?
其實這個問題已經不太須要再查資料驗證了,咱們本身就能夠分析出結果。 棧內存主要描述的是java方法執行的內存模型,一個方法內部的局部變量生命週期跟隨方法的開始和結束,管理起來很是簡單,因此方法內部的局部變量保存在棧中沒毛病! 可是對象內部的基礎類型成員變量呢?這個生命週期就比較複雜了,有明確的開始卻沒有明確的結束,放到棧中明顯沒法管理。因此,只有方法內的基礎類型變量纔會保存在棧中!
第四個問題:咱們知道棧的回收效率和速度遠大於堆,爲何這一塊的管理如此簡單快速呢?
繼續引用《深刻理解java虛擬機》一段話來描述java棧內存:
常常有人把java內存區分爲堆內存和棧內存,這種分法比較粗糙,java內存區域的劃分實際上遠比這發雜。這種劃分方式的流行只能說明大多數程序員最關注的、與對象內存分配關係最密切的內存區域是這兩塊。其中所指的「棧」就是如今講的虛擬機棧,或者說是虛擬機棧中局部變量表部分。
局部變量表存放了編譯器可知的各類基本數據類型、對象引用和returnAddress類型。 其中64位長度的long和double類型的數據會佔用2個局部變量空間(slot),其他的數據類型只佔用1個。局部變量表所需的內存空間在編譯期完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。
第五個問題:如此簡單高效的棧內存,會內存溢出嗎?
雖然大部分虛擬機的棧均可以動態擴展,但也並非無限的。雖然棧溢出的狀況比較小,但當有大量方法調用時很是有可能引發棧溢出,很是典型的案例就是遞歸調用。
解決了兩個常見的誤解或沒有關注的點:GC運行在棧仍是堆仍是二者都有;全部基本類型變量都保存在棧中嗎?
java GC僅運行在堆內存上,回收效率較低。 棧內存僅描述了java方法的內存模型,每一個方法所需的內存空間在編譯器就已經確認,利用棧的特性完成內存分配和回收。 基礎類型變量僅方法內的局部變量保存在棧內存中,成員變量保存在堆中。
雖然沒有定量的標準,也沒有辦法得出優化先後的對比量。但在開發中,咱們應該有意識的減小成員變量,若是數據能夠在方法間傳遞,就沒有必要利用成員變量作中間狀態保存。同時業務開發避免方法的遞歸調用。