運行時內存區域這塊,如果不將內存各個區域做什麼的瞭解清楚,後面看的會很累。
之前將JVM運行時內存區域的內容,整理在了一篇文章中。
在後續深入、細緻的學習中,整理的內容越來越多,一篇的話,會導致篇幅過長。
所以將《JVM運行時內存區域詳解》分爲以下幾個章節:
這裏將《Java虛擬機規範中文版》上傳了,點擊下面鏈接,即可下載
目錄
《Java Virtual Machine Specification Java SE 7 中文版》
《Java Virtual Machine's Internal Architecture》
Java虛擬機棧
《深入理解Java虛擬機:JVM高級特性與最佳實踐》
Java虛擬機棧是線程私有的,它的生命週期與線程相同。
Java虛擬機棧描述的是Java方法執行的內存模型:
每個方法在執行的同時都會創建一個棧幀,它用於存儲局部變量、操作數棧、動態鏈接、方法出口等信息。
每一個方法從調用直到執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
《Java Virtual Machine Specification Java SE 7 中文版》
每一條Java虛擬機都有自己私有的Java虛擬機棧,這個棧與線程同時創建,用於存儲棧幀。
Java虛擬機棧的作用,就是用於存儲局部變量與一些過程結果的地方。
另外,它在方法調用和返回中也扮演了重要的角色。因爲除了棧幀的出棧和入棧之外,Java虛擬機棧不會再受其他因素的影響。
Java 虛擬機規範允許 Java 虛擬機棧被實現成固定大小的或者是根據計算動態擴展和收縮的。
如果採用固定大小的 Java 虛擬機棧設計,那每一條線程的 Java 虛擬機棧容量應當在線程創建的時候獨立地選定。
Java 虛擬機實現應當提供給程序員或者最終用戶調節虛擬機棧初始容量的手段,對於可以動態擴展和收縮 Java 虛擬機棧來說,則應當提供調節其最大、最小容量的手段。
舉個栗子:
User user = new User();
這裏的user就是對象的引用,也可以理解爲地址,指引着虛擬機要去哪裏找user這個對象。如下圖:
(圖中爲了方便只標出了動態鏈接user1、user2、user3...以此代替棧幀的展示...)
(圖片來自網絡)
由圖可知,當我們將一個對象作爲方法的參數時,在方法中修改這個對象的值,也會影響到原來的對象,因爲我們只是改變了圖中內存區域的值,它的指引(地址)還是一樣的。
同時可以看出,棧中的內存區域是連續的,有大小限制的,如果超過了就會拋出棧溢出的異常StackOverflowError。
在每個方法執行的時候,都會創建一個個棧幀,用於保存局部變量,操作數棧、動態鏈表等信息。
每次方法的調用都會對應着一個棧幀,因此可以解釋有我們在寫遞歸程序的時候不小心報棧溢出的異常,因爲棧時有限的,方法調用太多次導致棧幀存滿,所以溢出。
《Java Virtual Machine's Internal Architecture》
啓動線程的的時候,Java虛擬機會爲該線程創建新的Java虛擬機棧。
Java虛擬機棧將線程的狀態存儲在棧中。
Java虛擬機在Java虛擬機棧中只執行兩個操作:入棧和出棧。
當線程調用Java方法時,虛擬機會創建一個新棧幀並將其壓入當前線程的Java虛擬機棧中。
然後這個新棧幀就成爲了當前棧幀。
當方法執行的時,使用該棧幀來存儲參數、局部變量、中間計算和其他數據。
方法可以兩種方式(之一)完成;正常完成、異常完成。
無論是正常還是突然,Java虛擬機都會彈出並丟棄當前方法的棧幀;然後,先前方法的棧幀變爲當前幀。
堆與棧
在網上找到一篇不錯的博文,這裏摘錄下重要的內容。
建議大家看下原文:
《JVM調優總結(一)-- 一些概念》http://hllvm.group.iteye.com/group/wiki/2858-JVM
棧是運行時的單位,而堆是存儲的單位
棧解決程序的運行問題,即程序如何執行,或者說如何處理數據;堆解決的是數據存儲的問題,即數據怎麼放、放在哪兒。
在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因爲不同的線程執行邏輯有所不同,因此需要一個獨立的線程棧。而堆則是所有線程共享的。
棧因爲是運行單位,因此裏面存儲的信息都是跟當前線程(或程序)相關信息的。包括局部變量、程序運行狀態、方法返回值等等;而堆只負責存儲對象信息。
爲什麼要把堆和棧區分出來呢?棧中不是也可以存儲數據嗎
第一,從軟件設計的角度看,棧代表了處理邏輯,而堆代表了數據。這樣分開,使得處理邏輯更爲清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設計的方方面面都有體現。
第二,堆與棧的分離,使得堆中的內容可以被多個棧共享(也可以理解爲多個線程訪問同一個對象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數據交互方式(如:共享內存),另一方面,堆中的共享常量和緩存可以被所有棧訪問,節省了空間。
第三,棧因爲運行時的需要,比如保存系統運行的上下文,需要進行地址段的劃分。由於棧只能向上增長,因此就會限制住棧存儲內容的能力。而堆不同,堆中的對象是可以根據需要動態增長的,因此棧和堆的拆分,使得動態增長成爲可能,相應棧中只需記錄堆中的一個地址即可。
第四,面向對象就是堆和棧的完美結合。其實,面向對象方式的程序與以前結構化的程序在執行上沒有任何區別。但是,面向對象的引入,使得對待問題的思考方式發生了改變,而更接近於自然方式的思考。當我們把對象拆開,你會發現,對象的屬性其實就是數據,存放在堆中;而對象的行爲(方法),就是運行邏輯,放在棧中。我們在編寫對象的時候,其實即編寫了數據結構,也編寫的處理數據的邏輯。不得不承認,面向對象的設計,確實很美。