jvm(1):jvm記憶體模型
- 2019 年 10 月 4 日
- 筆記
身為一個職業的Java程式設計師,每天打交到最多的就是jvm,那麼套用孫子的一句話「知己知彼方能百戰不殆」,熟悉jvm也就意味著是我們進階路上必過之檻,下面先來張圖,大概說明下jvm的記憶體分布

從圖上可以看出Java記憶體主要分5個部分,下面會針對這5個部分分別進行說明
方法區:主要是保存的資訊是類的元數據。方法區與堆空間類似,是被JVM中所有的執行緒共享的區域。方法區中最為重要的是類的類型資訊、常量池、域資訊、方法資訊。類型資訊包括類的完整名稱、父類的完整名稱、類型修飾符和類型的直接介面。常量池包括類方法、域等資訊所引用的常量資訊。域資訊包括域名稱、域類型和域修飾符、方法資訊包括方法名稱、返回類型、方法參數、方法修飾符、方法位元組碼、操作數棧和方法棧幀的局部變數區大小以及異常表。方法區是執行緒間共享的,當兩個執行緒同時需要載入一個類型時,只有一個類會請求ClassLoader載入,另一個執行緒則會等待。
Java堆:堆在JVM規範里是一種通用性的記憶體池,用於存放所有的Java對象。堆是一個運行時數據區,類的對象從中分配空間,堆的優勢是可以動態地分配記憶體大小,生存周期也不需要事先告訴編譯器。
因為這裡講到了Java堆,所有有必要講一下Java堆的優化,1是將生命周期較長的Java對象從heap中移到heap之外,並且GC不能管理GCIH內部的Java對象,以此達到降低GC的回收頻率和提升GC的回收效率的目的,另外逃逸分析與棧上分配這樣的優化技術同樣也是降低GC回收頻率和提升GC回收效率的有效方式。
下面來說下逃逸分析。如果一個對象的指針被多個方法或執行緒引用時,那就可以稱這個指針發生了逃逸
public class escapeAnalysisClass{ public static B b; public void globalVariablePointerEscape(){//給全局變數賦值,發生逃逸 b = new B(); } public B methodPointerEscape(){//方法返回值,發生逃逸 return new B(); } public void instancePassPointerEscape(){ methodPointerEscape().printClassName(this);//實例引用發生逃逸 } }
通過上面的例子我們可知,逃逸分析通常是全局變數賦值、方法返回值、實例引用傳遞(詳見另一篇文章)
虛擬機棧:JVM的架構是基於棧的,即程式指令的每一個操作都要經過入棧和出棧這樣的組合型操作才能完成。虛擬機棧是一種可以被用來快速訪問的存儲區域,該區域位於通用RAM裡面,通過使用它的所謂的「」棧指針」,可以訪問處理器。棧是一種快速有效的分配存儲方法,訪問速度僅次於暫存器,堆棧指針若向下移動,則分配新的記憶體,若向上移動,則釋放那些記憶體。由於Java虛擬機需要預先去生成相應的記憶體空間,所以但我們嘗試運行程式的時候,Java虛擬機必須知道被存儲在棧內的所有數據的確切大小和生命周期,以便按照上面陳述的分配存儲方法通過上下移動堆棧指針來動態調整記憶體空間。
綜上對比堆和棧,可知棧的優勢是訪問速度比堆要快,它僅次於暫存器,並且棧數據是可以被共享的。棧的缺點是存儲在棧裡面的數據大小與生存期必須是確定的,從這一點來看,棧明顯缺乏靈活性。
Java虛擬機棧也是執行緒私有的記憶體空間,它和Java執行緒在同一時間創建,它保存方法的局部變數、部分結果,並參與方法的調用和返回。
再來深入探討虛擬機棧的內部結構。

虛擬機棧在運行時使用一種叫作棧幀的數據結構保存上下文數據,棧幀裡面存放了方法的局部變數表、操作數棧、動態鏈接方法和返回地址等資訊。每一個方法的調用都伴隨著棧幀的入棧操作,相對的,方法的返回則表示棧幀的出棧操作。如果方法調用時,方法的參數和局部變數相對較多,那麼棧幀中的局部變數表就會比較大,棧幀會不斷膨脹以滿足方法調用所需傳遞的資訊增大需求。
棧幀由三部分組成,即局部變數取,操作數棧和幀數據區。
程式計數器:當前執行緒所執行的位元組碼的行號指示器。在多執行緒環境下,為了讓執行緒切換後能恢復到正確的執行位置,每個執行緒都要一個獨立的程式計數器,各個執行緒之間互不影響、獨立存儲,因此這塊記憶體是執行緒私有的。
本地方法棧:本地方法棧用於管理本地方法的調用。