由Java虛擬機執行的編譯代碼使用與硬件和操做系統無關的二進制格式表示,一般存儲在 class文件
中。class文件
精確地定義了類或接口的表示形式,包括在特定於平臺的目標文件格式中可能被視爲理所固然的字節排序等細節。程序員
與Java 編程語言同樣,Java 虛擬機對兩種類型進行操做:基本類型
和 引用類型
。相應地,有兩種類型的值能夠存儲在變量中,做爲參數傳遞,由方法返回,並對其進行操做:基本值
和 引用值
。編程
Java 虛擬機指望幾乎全部類型檢查都在運行時以前完成,一般由編譯器完成,而沒必要由 Java 虛擬機自己完成。基本類型的值不須要被標記,也不須要在運行時檢查以肯定它們的類型,或者與引用類型的值區分開來。相反,Java 虛擬機的指令集使用對特定類型的值進行操做的指令來區分其操做數類型。例如,iadd、ladd、fadd 和 dadd 都是添加兩個數值併產生數值結果的 Java 虛擬機指令,但每一個指令都專門針對其操做數類型: int、long、float 和 double。數組
Java 虛擬機包含對對象的顯式支持。對象要麼是動態分配的類實例,要麼是數組。對對象的引用被認爲具備 Java 虛擬機類型引用。能夠將引用類型的值視爲指向對象的指針。一個對象可能存在多個引用。對象老是經過引用類型的值進行操做、傳遞和測試。數據結構
Java虛擬機支持的基本數據類型有 數值類型
、布爾類型
和returnAddress類型
。框架
數值類型由 整數類型
和 浮點類型
組成:編程語言
整數類型包括:byte、short、short、int、long 和 char。函數
浮點類型包括:float 和 double。測試
布爾類型
的值將 true 和 false 編碼,默認值爲false。編碼
returnAddress類型
的值是指向 Java 虛擬機指令操做碼的指針。在基本類型中,只有 returnAddress類型
與 Java 編程語言類型沒有直接關聯。操作系統
有三種引用類型:類類型
、數組類型
和 接口類型
。它們的值分別是對動態建立的類實例、數組或實現接口的類實例或數組的引用。
數組類型
由具備單個維度的組件類型組成(其長度不禁類型給出)。數組類型
的組件類型自己能夠是 數組類型
。若是從任何 數組類型
開始,先考慮其組件類型,而後再考慮(若是也是數組類型)該類型的組件類型,依此類推,則最終必須達到不是 數組類型
的組件類型,這稱爲數組類型的元素類型。數組類型的元素類型必須是原始類型,類類型或接口類型。
引用值
也能夠是特殊的空引用,一個對 no 對象的引用,在這裏用 null 表示。空引用最初沒有運行時類型,但能夠轉換爲任何類型。引用類型的默認值是 null。
Java 虛擬機定義了在程序執行期間使用的各類運行時數據區域。其中一些數據區域是在Java 虛擬機啓動時建立的,只有在Java虛擬機退出時纔會銷燬。其餘數據區域是在每一個線 程建立時建立,在線程退出時銷燬。
Java 虛擬機能夠同時支持多個線程執行。每一個 Java 虛擬機線程都有本身的 程序計數器
。在任什麼時候候,每一個 Java 虛擬機線程都在執行單個方法的代碼,即該線程的當前方法。若是該方法不是 native,程序計數器
包含當前正在執行的 Java 虛擬機指令的地址。若是線程當前執行的方法是 native,則 Java 虛擬機的 程序計數器
的值是未定義的。Java 虛擬機的 程序計數器
足夠寬,能夠容納特定平臺上的 returnAddress 或本機指針。
每一個 Java 虛擬機線程都有一個私有的 Java 虛擬機棧
,與線程同時建立。Java 虛擬機棧
存儲 棧幀。Java 虛擬機棧
相似於 C 等傳統語言的棧:它持有局部變量和部分結果,並在方法調用和返回中起做用。由於除了 push 和 pop 棧幀 外,Java 虛擬機棧
歷來沒有被直接操做過,因此能夠對棧幀進行堆分配。Java虛擬機棧的內存不須要是連續的。
Java 虛擬機棧具備固定的大小,或者根據計算的須要動態擴展和收縮。若是 Java 虛擬機棧的大小是固定的,則能夠在建立棧時獨立選擇每一個 Java 虛擬機棧的大小。
Java 虛擬機實現能夠爲程序員或用戶提供對 Java 虛擬機棧
初始大小的控制,在動態擴展或收縮 Java 虛擬機棧
的狀況下,還能夠提供對最大和最小大小的控制。
下列異常狀況與Java 虛擬機棧
相關:
Java 虛擬機棧
,則 Java 虛擬機將拋出一個 StackOverflowError
異常。Java虛擬機棧
,Java 虛擬機拋出一個 OutOfMemoryError
異常。Java 虛擬機的實現可使用傳統棧(俗稱「C棧」)來支持 native 方法(用 Java 編程語言之外的語言編寫的方法)。 和 Java 虛擬機棧
同樣,本地方法棧也會拋出 StackOverflowError
和 OutOfMemoryError
異常。
Java 虛擬機有一個在全部 Java 虛擬機線程之間共享的 堆
。堆是爲全部類實例和數組分配內存的運行時數據區域。
堆
是在虛擬機啓動時建立的。對象的 堆
存儲由自動存儲管理系統(稱爲 垃圾收集器
)回收;對象從不顯式釋放。Java 虛擬機沒有特定類型的自動存儲管理系統,能夠根據實現者的系統需求選擇存儲管理技術。堆
的大小能夠是固定的,也能夠根據計算的須要進行擴展,若是不須要更大的堆,則能夠收縮。堆的內存不須要是連續的。
Java 虛擬機實現能夠爲程序員或用戶提供對 堆
初始大小的控制,若是能夠動態擴展或收縮堆,還須要能夠控制 堆
的最大和最小大小。
下列異常狀況與 堆
相關:
堆
比自動存儲管理系統提供的 堆
多,則 Java 虛擬機拋出OutOfMemoryError
。Java 虛擬機具備一個在全部 Java 虛擬機線程之間共享的 方法區
。方法區
相似於常規語言的編譯代碼的存儲區域,或者相似於操做系統過程當中的「文本」段。它存儲每一個類的結構,例如運行時常量池,字段和方法數據,以及方法和構造函數的代碼,包括用於類和接口初始化以及實例初始化的特殊方法。
方法區
是在虛擬機啓動時建立的。儘管 方法區
在邏輯上是堆的一部分,可是簡單的實現能夠選擇不進行垃圾回收或壓縮。沒有規定· 方法區
的位置或用於管理已編譯代碼的策略。方法區
能夠是固定大小的,或者能夠根據計算的須要進行擴展,若是不須要更大的方法區域,則能夠縮小。方法區
的內存沒必要是連續的。
Java 虛擬機實現能夠爲程序員或用戶提供對方法區
初始大小的控制,而且在方法區域大小可變的狀況下,能夠控制最大和最小 方法區
大小。
如下異常條件與 方法區
相關聯:
方法區
中的內存來知足分配請求,則 Java 虛擬機將拋出一個 OutOfMemoryError
。運行時常量池
是 class 文件
中 constant_pool 表的每一個類或每一個接口的運行時表示。它包含幾種類型的常量,從編譯時已知的數值常量到必須在運行時解析的方法和字段引用。運行時常量池
的功能相似於傳統編程語言的符號表,儘管它包含的數據範圍比典型的符號表更廣。
每一個 運行時常量池
都是從 Java 虛擬機的 方法區
分配的。類或接口的 運行時常量池
是在Java虛擬機建立類或接口時構造的。
下列異常狀況與類或接口的運行時常量池的構造有關:
方法區
所能提供的內存,Java 虛擬機拋出 OutOfMemoryError
。棧幀
用於存儲數據和部分結果,以及執行動態連接、方法的返回值和調度異常。
每次調用一個方法都會建立一個新 棧幀
。不管完成是正常的仍是異常的(它拋出一個未捕獲的異常),當它的方法調用完成時,一個 棧幀
將被銷燬。棧幀
是從建立 棧幀
的線程的 Java 虛擬機棧中分配的。每一幀都有本身的 局部變量
數組,本身的 操做數棧
,以及對當前方法類的 運行時常量池
的引用。
可使用附加的特定於實現的信息(如調試信息)對 棧幀
進行擴展。
局部變量數組
和 操做數棧
的大小是在編譯時肯定的,並隨與 棧幀
關聯的方法的代碼一塊兒提供。所以,棧幀
數據結構的大小隻取決於 Java 虛擬機的實現,而且能夠在方法調用時同時分配這些結構的內存。
在給定的控制線程中,只有一個幀(執行方法的幀)是活動的。這個幀稱爲當前幀,它的方法稱爲當前方法。定義當前方法的類是當前類。對 局部變量 和 操做數棧 的操做一般與當前幀相關。
若是一個幀的方法調用另外一個方法,或者該幀的方法完成,則該幀將中止爲當前幀。當調用一個方法時,將建立一個新 棧幀
,並在控制轉移到新方法時成爲當前 棧幀
。在方法返回時,當前幀將其方法調用的結果(若是有的話)傳回前一幀。當前一幀成爲當前幀時,當前幀將被丟棄。
注意,線程建立的 棧幀
是該線程的本地 棧幀
,不能被任何其餘線程引用。
每一 棧幀
包含一組稱爲 局部變量
的變量。棧幀
的 局部變量
數組的長度在編譯時肯定,並以類或接口的二進制表示形式提供,同時提供與幀相關的方法的代碼。
單個 局部變量
能夠保存類型爲 boolean、byte、char、short、int、float、reference或returnAddress的值。一對 局部變量
能夠包含 long 或 double 類型的值。
局部變量
經過索引尋址。第一個 局部變量
的索引爲 0。當且僅當該整數比局部變量數組的大小小 0 到 1 之間時,該整數被認爲是 局部變量
數組的索引。
long 或 double 類型的值佔用兩個連續的局部變量。這樣的值只能使用較小的索引來處理。例如,在索引 n 處存儲在局部變量數組中的 double 類型的值實際上佔用了索引爲 n 和 n+1 的局部變量;可是,沒法從索引 n+1 處加載局部變量。它能夠存儲。可是,這樣作會使局部變量 n 的內容無效。
Java 虛擬機不須要 n 是偶數。直觀地說,long 和 double 類型的值 在局部變量
數組中沒必要是 64 位對齊的。實現者能夠自由決定使用爲該值保留的兩個 局部變量
來表示這些值的適當方法。
Java 虛擬機使用本地變量在方法調用時傳遞參數。在類方法調用中,任何參數都是在連續的 局部變量
中傳遞的,從 局部變量 0 開始。在實例方法調用中,老是使用局部變量 0 將引用傳遞給調用實例方法的對象(在Java編程語言中)。任何參數隨後都在從局部變量 1 開始的連續局部變量中傳遞。
每一個 棧幀
都包含一個後進先出(LIFO)堆棧,稱爲操做數棧
。幀的操做數棧
的最大深度是在編譯時肯定的,並與幀關聯的方法的代碼一塊兒提供。
當上下文清楚時,咱們有時會將當前幀的操做數棧簡單地稱爲操做數棧
。
建立包含操做數棧的 棧幀
時,該 操做數棧
爲空。Java 虛擬機提供將常量或值從局部變量或字段加載到操做數棧的指令。其餘 Java 虛擬機指令從操做數棧中獲取操做數,對它們進行操做,並將結果推回操做數棧。操做數棧還用於準備傳遞給方法的參數和接收方法結果。
例如,iadd 指令將兩個 int 值相加。它要求將 int 值添加到 操做數棧
的前兩個值中,這是由前面的指令推入的。兩個 int 值都是從 操做數棧
中彈出的。它們被添加,它們的和被推回 操做數棧
。子計算能夠嵌套在 操做數棧
上,從而產生可由包含計算使用的值。
操做數棧
上的每一個條目能夠包含任何 Java 虛擬機類型的值,包括 long 或 double 類型的值。
操做數堆棧中的值必須以適合其類型的方式操做。例如,不可能推兩個 int 值,而後將它們視爲 long 值;也不可能推兩個浮點值,而後用 iadd 指令將它們相加。少許的 Java 虛擬機指令(dup 指令和swap)做爲原始值對運行時數據區域進行操做,而不考慮它們的具體類型;這些指令的定義方式使它們不能用於修改或分解單個值。這些操做數棧操做的限制是經過類文件驗證來實施的。
在任什麼時候候,操做數堆棧都有一個關聯的深度,其中 long 或 double 類型的值爲深度貢獻兩個單位,其餘類型的值爲深度貢獻一個單位。
每一 棧幀
都包含對當前方法類型的 運行時常量池
的引用,以支持方法代碼的 動態連接
。方法的類文件代碼引用要調用的方法和要經過符號引用訪問的變量。動態連接
將這些符號方法引用轉換爲具體的方法引用,根據須要加載類來解析還沒有定義的符號,並將變量訪問轉換爲與這些變量的運行時位置相關的存儲結構中的適當偏移量。
方法和變量的這種後期綁定使方法使用的其餘類中的更改不太可能破壞此代碼。
若是方法調用沒有致使拋出異常,則方法調用正常完成,不管是直接從 Java 虛擬機拋出,仍是執行顯式拋出語句的結果。若是當前方法的調用正常完成,則可能向調用方法返回一個值。當被調用的方法執行一條返回指令時,就會發生這種狀況,對於返回的值的類型(若是有的話),必須選擇一條返回指令。
在這種狀況下,使用當前 棧幀
來恢復調用程序的狀態,包括它的局部變量
和 操做數棧
,並適當增長調用程序的 程序計數器
以跳過方法調用指令。而後在調用方法的框架中繼續正常執行,將返回的值(若是有的話)推入該 棧幀
的 操做數棧
。
若是在方法中執行 Java 虛擬機指令致使 Java 虛擬機拋出異常,而且該異常不在方法中處理,則方法調用將忽然結束。athrow 指令的執行也會致使顯式拋出異常,若是當前方法沒有捕獲異常,則會致使方法調用的異常完成。異常完成的方法調用永遠不會向調用者返回值。
Java 虛擬機不強制要求對象具備任何特定的內部結構。
Oracle 對 Java 虛擬機的實現,對類實例的引用句柄指針,指針自己就是一對:一個包含對象方法表的指針和一個指向類對象的指針,表示對象的類型,以及其餘爲對象從堆中分配的內存數