Java虛擬機 —— 運行時數據區

Java虛擬機內存,是指JVM的運行時數據區域,主要分爲:方法區、堆、虛擬機棧、本地方法棧、程序計數器。其中方法區和堆爲索引線程的共享數據區,而虛擬機棧、本地方法棧、程序計數器爲線程隔離的數據區。
html

程序計數器

每一個線程都有一個獨立的計數器用來記錄程序當前執行的指令,能夠當作是當前線程所執行的字節碼的行號指示器。若是線程正在執行Java方法,計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是執行的是Native方法,計數器記錄值爲空(Undefined)。程序計數器佔用的內存空間很是小,是線程的私有區域,此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。數組

虛擬機棧

虛擬機棧也是線程私有的,它的生命週期與線程相同。虛擬機棧是一個後進先出的數據結構,裏面存放的是棧幀,每一個Java方法的調用對應一個棧幀在虛擬機棧中的入棧和出棧。當線程執行一個Java方法執行時,就會建立一個新的棧幀並壓入到該線程的虛擬機棧的棧頂,Java方法執行結束後棧頂的該棧幀就會彈出棧並銷燬。數據結構

棧幀裏面存放的是Java方法執行的一些數據,包括局部變量表、操做數棧、動態鏈接、方法出口等。
this

局部變量表

局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。.net

局部變量表的容量以變量槽(Slot)爲最小單位,32位虛擬機中一個Slot能夠存放一個32位之內的數據類型(boolean、byte、char、short、int、float、reference和returnAddress八種)。reference類型虛擬機規範沒有明確說明它的長度,但通常來講,虛擬機實現至少都應當能今後引用中直接或者間接地查找到對象在Java堆中的起始地址索引和方法區中的對象類型數據。returnAddress類型是爲字節碼指令jsr、jsr_w和ret服務的,它指向了一條字節碼指令的地址。線程

Java虛擬機是使用局部變量表完成參數值到Java方法參數變量列表的傳遞過程的,若是是實例方法(非static),那麼局部變量表的第0位索引的Slot默認是用於傳遞方法所屬對象實例的引用,在方法中經過this訪問。code

Slot是能夠重用的,下一次分配Slot的時候,將會覆蓋原來的數據。Slot對對象的引用會影響GC(要是被引用,將不會被回收)。orm

操做數棧

操做數棧也常被稱爲操做棧,一樣是一個後進先出的數據結構。當一個方法剛剛開始執行的時候,這個方法的操做數棧是空的,在方法的執行過程當中,會有各類字節碼指令向操做數棧中寫入和提取內容,也就是入棧出棧操做。在作算術運算的時候是經過操做數棧來進行的,在調用其餘方法的時候是經過操做數棧來進行參數傳遞的。JVM將操做數棧做爲工做區。JVM沒有寄存器,全部的參數傳遞和返回值都是基於操做數棧來完成的。cdn

好比,執行引擎執行c = a + b時,會先被操做的參數ab壓入操做數棧,而後操做指令將他們彈出棧,並執行操做,將結果再壓入棧。htm

Java虛擬機的解釋執行引擎稱爲「基於棧的執行引擎」,其中所指的「棧」就是操做數棧。

動態鏈接

每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。Class文件的常量池有存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉化爲直接引用,這種轉化稱爲靜態解析。另一部分將在每一次的運行期間轉化爲直接引用,這部分稱爲動態鏈接。

方法出口(返回地址)

當一個方法被執行後,有兩種方式退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者(調用當前方法的方法稱爲調用者),是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法的方式稱爲正常完成出口(Normal Method Invocation Completion)。

另一種退出方式是,在方法執行過程當中遇到了異常,而且這個異常沒有在方法體內獲得處理,不管是Java虛擬機內部產生的異常,仍是代碼中使用athrow字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會致使方法退出,這種退出方法的方式稱爲異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的上層調用者產生任何返回值的。

不管採用何種退出方式,在方法退出以後,都須要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。通常來講,方法正常退出時,調用者的PC計數器的值就能夠做爲返回地址,棧幀中極可能會保存這個計數器值。而方法異常退出時,返回地址是要經過異常處理器來肯定的,棧幀中通常不會保存這部分信息。

方法退出的過程實際上等同於把當前棧幀出棧,所以退出時可能執行的操做有:恢復上層方法的局部變量表和操做數棧,把返回值(若是有的話)壓入調用者棧幀的操做數棧中,調整PC計數器的值以指向方法調用指令後面的一條指令等。

虛擬機棧Error

Java虛擬機棧有可能出現的error就是StackOverflowErrorOutOfMemoryError。當線程請求的棧深度大於Java虛擬機棧容許的深度時,就會拋出StackOverflowError錯誤。好比將一個方法反覆遞歸,最終就會出現StackOverflowError。當Java虛擬機棧能夠動態擴展時(大部分的 Java 虛擬機均可動態擴展,不過 Java 虛擬機規範中也容許固定長度的虛擬機棧),若是沒法申請到足夠的內存來擴展棧,就會拋出OutOfMemoryError錯誤。

本地方法棧

本地方法棧與虛擬機棧的功能相似,他們的區別在於虛擬機棧爲執行Java代碼方法服務,而本地方法棧是爲Native方法服務。本地方法棧就是一個C的方法棧,本地方法棧的參數順序、返回值和典型的C程序相同,本地方法通常來講能夠(依賴 JVM 的實現)反過來調用 JVM 中的 Java 方法。這種native方法調用Java會發生在棧(通常是Java棧)上,線程將離開本地方法棧,並在 Java 棧上開闢一個新的棧幀。

與虛擬機棧同樣,本地方法棧也會拋出StackOverflowErrorOutOfMemoryError

堆是Java虛擬機中最大的一塊內存區域,它是有全部的線程共享。幾乎全部的實例對象和數組都是在堆中存放。只要是經過new關鍵字建立對象或者直接聲明數組,都會在堆中開闢內存空間來存放。由於在棧幀被建立後沒法調整大小,棧幀中只能存放對象和數組在堆中的引用。方法或線程結束時對象和數組不會當即被移除銷燬,它只能由垃圾回收器回收。

一樣地,若是在堆中沒有內存來完成實例分配,而且堆也沒法擴展時,將會拋出OutOfMemoryError

方法區

方法區與堆同樣,是各個線程共享的內存區域,它存儲已經被虛擬機加載的類信息(包括字段信息、方法信息、方法代碼等)、常量、靜態變量、即時編譯器編譯後的代碼等數據。

方法區中的內存通常不會被GC回收,GC也很難回收。方法區的內存回收主要是針對針對常量池的回收和對類的卸載。根據 Java 虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。

運行時常量池

運行時常量池是方法區的一部分,Class文件除了有關類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。運行時常量池能夠理解爲是類或接口的常量池的運行時表現形式。


參考:
Java虛擬機內存區域劃分詳解
JAVA內存結構之運行時棧幀結構

相關文章
相關標籤/搜索