JVM運行時數據區域和JVM內存模型實例

Java虛擬機使Java成為了一種跨平臺的語言,Java不直接與操作系統接觸,而是通過虛擬機這個中間橋樑,通過JVM與底層接觸。不同的系統有不同的JVM,但是所有的這些JVM都完美的支持Java語法,這就使得WriteOnce,RunEveryWhere成為可能。

除此之外,JVM的內存管理機制使得不需要再為每一個new操作去delete/free代碼,由機器代替程序員這樣就不容易出現內存洩露和內存溢出的問題了,但是一旦出現了這種問題如果不瞭解JVM是怎樣使用內存的,那麼排查錯誤將會非常困難。

JVM運行時數據區域

JVM分為幾個區域,如下圖。有的區域隨著虛擬機進程的啟動而存在,有的區域則依賴用戶進程的啟動和結束而創建和銷燬。

1.程序計數器

解釋:【指向當前線程所執行的字節碼的行號】,其實就是一小塊內存,記錄著當前程序運行到哪了。字節碼解釋器的工作就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。分支,循環,跳轉,異常處理,線程回覆等都需要依賴這個計數器來完成。

由於Java多線程是通過線程輪流切換完成的,一個線程沒有執行完時就需要一個東西記錄它執行到哪了,下次搶佔到了CPU資源時再從這開始,這個東西就是程序計數器,正是因為這樣,所以它也是“線程私有”的內存。

如果一個線程執行一個main方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是一個Native方法,這個計數器的值則為空,此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。

2.Java虛擬機棧

與程序計數器一樣,Java虛擬機棧也是線程私有的,虛擬機棧描述的是Java方法執行的內存模型,每個方法在執行的同時都會創建一個棧楨用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息,

通常所說的JVM裡的堆和棧裡這個棧就是Java虛擬機棧,或者說是Java虛擬機棧中的局部變量表部分。

局部變量表存放了編譯期可知的各種基本數據類型,對象引用(僅限局部變量的,不包含成員變量的)。其中每個局部變量空間(Slot)有32位,所以long和double類型的數據會佔用兩個局部變量空間,其他類型包括對象引用佔用一個。對象引用調用的是存在堆中的對象,這個引用可以是對象的起始地址或者是指向對象的句柄(在內存模型中會介紹)。局部變量表所需的內存在編譯期就已經確定了也就是進入這個方法時就已經確定了,運行期間不會更改。

操作數棧則存儲方法內一些進行了運算操作後的結果。

動態鏈接,在方法內調用接口,通過字面量鏈接到具體的實現類,實現Java的動態特性。

方法出口,return或者發生Exception等。

如果方法methodOne方法調用了methodTwo,那麼methodOne就會先入棧創建一個棧楨,接著methodTwo再入棧成為棧頂(假設沒有其他的方法執行),methodTwo執行完先出棧,接著methodOne執行完出棧。

在使用遞歸的情況下,如果線程請求的棧的深度超過虛擬機所允許棧的深度就會拋出StackOverflowError;但是大部分虛擬機棧的深度都可以動態擴展,HotSpot中使用XSS可以設置棧的深度,如果擴展時無法請求到足夠的內存就會拋出OutOfMemoryError。

3.本地方法棧

本地方法棧和虛擬機棧相似,區別就是虛擬機為虛擬機棧執行Java服務(字節碼服務),而本地方法棧為虛擬機使用到的Native方法服務。本地方法棧中使用的語言,使用方式,數據結構沒有強制要求。

4.Java堆

堆是JVM裡最大的一塊內存區域,被所有線程共享,在虛擬機啟動時創建,此區域的目的就是存放對象實例和數組,幾乎所有的對象實例都在這分配(隨著JIT的發展已經不是那麼絕對了)。Java堆是垃圾收集管理的主要區域,由於現在收集器基本都採用分代收集方法,所以Java堆中還可以分為新生代,老年代,永久代。1.8之後取消了永久代;其中新生代又劃分為Eden空間,FromSurvivor空間,ToSurvivor空間。無論怎麼劃分都是為了更好的回收,分配,利用內存。

根據Java虛擬機規範,Java堆可以處於物理不連續的空間中,只要邏輯連續即可。在實現時,既可以實現成固定大小的也可以是可擴展的(通過-Xmx和-Xms控制),如果堆中沒有足夠的內存完成實例分配,並且堆也無法得到擴展時,將會拋出OutOfMemoryError異常。

5.方法區

方法區也是一個線程共享的區域,存儲已被虛擬機加載的類信息,常量(final),靜態變量(static),JIT(即時編譯器)編譯後的代碼等數據。虛擬機對方法區規範非常寬鬆,除了和Java堆一樣不需要連續的內存和可以選擇固定大小意外,還可以選擇不實現垃圾回收。垃圾回收行為在這個區域比較少見但還是有必要的,主要是針對常量池回收和類型的卸載。


分享到:


相關文章: