JVM內存結構(運行時數據區)
前言
Java程序的運行是經過Java虛擬機來實現的。經過類加載器將class字節碼文件加載進JVM,而後根據預約的規則執行。Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些內存區域被統一叫作運行時數據區。Java運行時數據區大體能夠劃分爲5個部分。在這裏要特別指出,咱們如今說的JVM內存劃分是概念模型。以下圖所示:
JVM運行時數據區分爲5種:java
-
程序計數器
-
虛擬機棧(java棧)
-
堆
-
方法區
-
本地方法棧
程序計數器
程序計數器是一塊較小的內存空間,它能夠當作是當前線程所執行的字節碼的行號指示器。程序計數器記錄線程當前要執行的下一條字節碼指令的地址。因爲Java是多線程的,因此爲了多線程之間的切換與恢復,每個線程都須要單獨的程序計數器,各線程之間互不影響。這類內存區域被稱爲「線程私有」的內存區域。
若是線程在執行的是一個Java方法,計數器記錄的是正在執行的虛擬機字節碼指令地址,若是正在執行的是Native方法,計數器值則爲空。因爲程序計數器只存儲一個字節碼指令地址,故此內存區域沒有規定任何OutOfMemoryError狀況。
虛擬機棧
與程序計數器同樣,虛擬機棧也是線程私有的,生命週期與線程相同。
虛擬機棧描述的是Java方法執行的內存模型,每一個方法執行的時候都會同時建立一個棧幀(用於存儲局部變量表、操做棧、動態連接、方法出口等信息)。
一個棧幀就表明了一個方法執行的內存模型,虛擬機棧中存儲的就是當前執行的全部方法的棧幀(包括正在執行的和等待執行的)。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。咱們平時所說的「局部變量存儲在棧中」就是指方法中的局部變量存儲在表明該方法的棧幀的局部變量表中。而方法的執行正是從局部變量表中獲取數據,放至操做數棧上,而後在操做數棧上進行運算,再將運算結果放入局部變量表中,最後將操做數棧頂的數據返回給方法的調用者的過程。
局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同與對象自己,根據不一樣的虛擬機實現,它多是一個指向對象起始地址的引用指針,也多是指向一個表明對象的句柄或者其餘與此對象相關的位置)和returnAddress類型(指向一條字節碼指令的地址)。
64爲長度的long和double類型的數據會佔2個局部變量空間(Slot),其他的數據類型只佔用1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底確實肯定的,在方法運行期間不會改變局部變量表的大小。
虛擬機棧可能出現兩種異常:由線程請求的棧深度過大超出虛擬機所容許的深度而引發的StackOverflowError異常;以及由虛擬機棧沒法提供足夠的內存而引發的OutOfMemoryError異常。
堆
Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例:全部的對象實例以及數組都要在堆上分配。但Class對象比較特殊,它雖然是對象,可是存放在方法區裏。
Java堆是垃圾收集器(GC)管理的主要區域。如今的收集器基本都採用分代收集算法:新生代和老年代。而對於不一樣的」代「採用的垃圾回收算法也不同。通常新生代使用複製算法;老年代使用標記整理算法。對於不一樣的」代「,通常使用不一樣的垃圾收集器,新生代垃圾收集器和老年代垃圾收集器配合工做。
Java堆能夠是物理上不連續的內存空間,只要邏輯上連續便可。Java堆可能拋出OutOfMemoryError異常。
方法區
方法區與Java堆同樣,是各個線程共享的內存區域。它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
全部的字節碼被加載以後,字節碼中的信息:類信息、類中的方法信息、常量信息、類中的靜態變量等都會存放在方法區。正如其名字同樣:方法區中存放的就是類和方法的全部信息。此外,若是一個類被加載了,就會在方法區生成一個表明該類的Class對象(惟一一種不在堆上生成的對象實例)該對象將做爲程序訪問方法區中該類的信息的外部接口。有了該對象的存在,纔有了反射的實現。
在Java7以前,HotSpot虛擬機中將GC分代收集擴展到了方法區,使用永久代來實現了方法區。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。可是在以後的HotSpot虛擬機實現中,逐漸開始將方法區從永久代移除。Java7中已經將運行時常量池從永久代移除,在Java 堆(Heap)中開闢了一塊區域存放運行時常量池。而在Java8中,已經完全沒有了永久代,將方法區直接放在一個與堆不相連的本地內存區域,這個區域被叫作元空間。
根據Java虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。
本地方法棧
本地方法棧與虛擬機棧相似,他們的區別在於:本地方法棧用於執行本地方法(Native方法);虛擬機棧用於執行普通的Java方法。在HotSpot虛擬機中,就將本地方法棧與虛擬機棧作在了一塊兒。
本地方法棧可能拋出的異常同虛擬機棧同樣。
運行時常量池
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、藉口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常亮池中。
運行時常量池相對於Class文件常量池的一個重要特徵是動態性,Java語言並不要求常量必定只能在編譯期產生,也就是並不是預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性用的比較多的是String類的intern()方法。
運行時常量池在JDK1.6及以前版本的JVM中是方法區的一部分,而在HotSpot虛擬機中方法區放在了」永久代(Permanent Generation)」。因此運行時常量池也是在永久代的。
可是JDK1.7及以後版本的JVM已經將運行時常量池從方法區中移了出來,在Java 堆(Heap)中開闢了一塊區域存放運行時常量池。
參考
《深刻理解Java虛擬機-JVM高級特性與最佳實踐》第二版 周志明著
歡迎關注本站公眾號,獲取更多信息