Java虛擬機內部結構——JVM8
JVM規範描述的是一種抽象化的虛擬機的行為,而不是任何一種廣泛使用的虛擬機實現。
要去「正確地」實現一台Java虛擬機,其實並不像大多數人所想的那樣高深和困難——只需要正確讀取class文件中每一條位元組碼指令,並且能正確執行這些指令所蘊含的操作即可。
所有在虛擬機規範之中沒有明確描述的實現細節,都不應成為虛擬機設計者發揮創造性的牽絆,設計者可以完全自主決定所有規範中不曾描述的虛擬機內部細節,
例如,運行時數據區的內存如何布局,選用哪種垃圾收集演算法,是否要對虛擬機位元組碼指令進行一些內部優化操作(如使用即時編譯器把位元組碼編譯為機器碼)。
1. Class文件格式
編譯後被Java虛擬機所執行的代碼使用了一種平台中立(不依賴於特定硬體及操作系統)的二進位格式來表示,並且經常(但並非絕對)以文件的形式存儲,因此這種格式稱為class文件格式。
class文件格式中精確地定義了類與介面的表示形式,包括在平台相關
的目標文件格式中一些細節上的慣例,例如位元組序( byte ordering)等。
2. 數據類型
與Java程序語言中的數據類型相似,Java虛擬機可以操作的數據類型可分為兩類:原始類型(primitivetype,也經常翻譯為原生類型或者基本類型)和引用類型(reference type)。
與之對應,也存在原始值( primitive value)和引用值(reference value)兩種類型的數值,它們可用於變數賦值、參數傳遞、方法返回和運算操作。
Java虛擬機是直接支持對象的。這裡的對象可以是指動態分配的個類的實例,也可以指某個數組。虛擬機中使用reference類型來表示對某個對象的引用。關於reference類型的值,你可以想像成指向對象的指針。每一個對象都可能存在多個指向它的引用,對象的操作、傳遞和檢查都通過引用它的reference類型的數據來進行。
3. 原始類型與值
3.1 數值類型,boolean類型和returnAddress類型
Java虛擬機所支持的原始數據類型包括數值類型(numeric type)、boolean類型和returnAddress類型三類。
數值類型又分為整數類型(integral type)和浮點類型(floating-point type,)兩種。
整數類型包括:
口byte類型:值為8位有符號二進位補碼整數,默認值為零。
口short類型:值為16位有符號二進位補碼整數,默認值為零。
口int類型:值為32位有符號二進位補碼整數,默認值為零。
口long類型:值為64位有符號二進位補碼整數,默認值為零。
口char類型:值為使用16位無符號整數表示的Unicode碼點,以UTF-16編碼,默認值為Unicode的null碼點( 『u0000』)。
浮點類型包括:
口float類型:單精度
口double類型:值為雙精度浮點數集合中的元素
boolean類型的值為布爾值true和false,默認值為false。
returnAddress類型是指向某個操作碼(opcode)的指針,此操作碼與Java虛擬機指令相對應。在虛擬機支持的所有原始類型中,只有returnAddress類型是不能直接與Java語言的數據類型相對應的。
3.2 整數類型的取值範圍
Java虛擬機中的整數類型的取值範圍如下:
口對於byte類型,取值範圍是-128~127 包括-128和127
口對於short類型-2』5~ 2』5-1
口對於int類型,取值範圍是-2』31~ 2』31-1
口對於long類型,取值範圍是-2』63~ 2』63-1
口對於char類型,取值範圍是O~65535,包括O和65535
3.3 returnAddress類型和值
returnAddress類型會被Java虛擬機的jsr、ret和jsr_w指令。所使用參returnAddress類型的值指向一條虛擬機指令的操作碼。與前面介
紹的那些數值類的原生類型不同,returnAddress類型在Java語言之中並不存在相應的
類型,而且也無法在程序運行期間更改。
3.4 boolean類型
雖然Java虛擬機定義了boolean這種數據類型,但是只對它提供了非常有限的支持。在Java虛擬機中沒有任何供boolean值專用的位元組碼指令,Java語言表達式所操作的boolean值,在編譯之後都使用Java虛擬機中的int數據類型來代替。
Java虛擬機直接支持boolean類型的數組。
在Oracle公司的虛擬機實現里,Java語言中的boolean數組將會被編碼成Java虛擬機
的byte數組,每個boolean元素佔8位。
Java虛擬機會把boolean數組元素中的true值採用1來表示,false值採用0來表
示,當Java編譯器把Java語言中的boolean類型值映射為Java虛擬機的int類型值時,
也必須採用上述表示方式。
4. 引用類型與值
Java虛擬機中有三種引用類型:類類型(class type)、數組類型(array type)和介面類型( interface type)。這些引用類型的值分別指向動態創建的類實例、數組實例和實現了某個介面的類實例或數組實例。
在引用類型的值中還有一個特殊的值:null,當一個引用不指向任何對象的時候,它的值就用null來表示。一個為null的引用,起初並不具備任何實際的運行期類型,但是
它可轉型為任意的引用類型。引用類型的默認值就是null。
Java虛擬機規範並沒有規定null在虛擬機實現中應當怎樣用編碼來表示。
5. 運行時數據區
Java虛擬機定義了若干種程序運行期間會使用到的運行時數據區,其中有一些會隨著虛擬機啟動而創建,隨著虛擬機退出而銷毀。另外一些則是與線程一一對應的,這些與線程對應的數據區域會隨著線程開始和結束而創建和銷毀。
5.1 pc寄存器
Java虛擬機可以支持多條線程同時執行,每一條Java虛擬機線程都有自己的pc(program counter)寄存器。
在任意時刻,一條Java虛擬機線程只會執行一個方法的代碼,這個正在被線程執行的方法稱為該線程的當前方法(current method)。如果這個方法不是native的,那pc寄存器就保存Java虛擬機正在執行的位元組碼指令的地址,
如果該方法是native的,那pc寄存器的值是undefined。
5.2 Java虛擬機棧
每一條Java虛擬機線程都有自己私有的Java虛擬機棧(Java Virtual Machine stack),這個棧與線程同時創建,用於存儲棧幀(Frame)。
Java虛擬機棧用於存儲局部變數與一些尚未算好的結果。另外,它在方法調用和返回中也扮演了很重要的角色。因為除了棧幀的出棧和入棧之外,Java虛擬機棧不會再受其他因素的影響,所以棧幀可以在堆(應該是java虛擬機棧)中分配,Java虛擬機棧所使用的內存不需要保證
是連續的。
Java虛擬機規範既允許Java虛擬機棧被實現成固定大小,也允許據計算動態來擴展和收縮。如果採用固定大小的Java虛擬機棧,那每一個線程的Java虛擬機棧容量可以在線程創建的時候獨立選定。
可能的異常:
StackOverflowError異常。
個OutOfMemoryError異常。
5.3 Java堆
在Java虛擬機中,堆(heap)是可供各個線程共享的運行時內存區域,也是供所有類實例和數組對象分配內存的區域。
Java堆在虛擬機啟動的時候就被創建,它存儲了gc垃圾收集器)所管理的各種對象,
受管理的對象無需也無法顯式地銷毀。
java堆的容量可以是固定的,也可以隨著程序執行的需求動態擴展,並在不需要過多空間時自動收縮。Java堆所使用的內存不需要保證是連續的。
可能異常:
OutOfMemoryError異常。
5.4方法區
在Java虛擬機中,方法區(methodarea)是可供各個線程共享的運行時內存區域。
它存儲了每一個類的結構信息,例如,
**運行時常量池( runtime constant pool)、
欄位和方法數據、
構造函數和普通方法的位元組碼內容,
還包括一些
在類、實例、介面初始化時用到的特殊方法)**。
方法區在虛擬機啟動的時候創建,雖然方法區是堆的邏輯組成部分,但是簡單的虛擬機實現可以選揮在這個區域不實現垃圾收集與壓縮。方法區在實際內存空間中可以是不連續的。
方法區可能發生如下異常情況:
OutOfMemoryError異常。
5.5運行時常量池
運行時常量池( runtime constantpool)是class文件中每一個類或介面的常量池表(constant_pool table)的運行時表示形式,
它包括了若干種不同的常量。
每一個運行時常量池都在Java虛擬機的方法區中分配,在載入糞和介面到虛擬機後,就創建對應的運行時常量池。
可能異常情況:
個OutOfMemoryError異常。
5.6本地方法棧
Java虛擬機實現可能會使用到傳統的棧(通常稱為Cstack)來支持native方法(指使用Java以外的其他語言編寫的方法)的執行,這個棧就是本地方法棧(native method stack)。
可能異常情況:
StackOverflowError異常,OutOfMemoryError異常。
6. 棧幀
棧幀(frame)是用來存儲數據和部分過程結果,用於支持虛擬機進行方法調用和方法執行的數據結構,同時也用來處理動態鏈接
( dynamic linking)、方法返回值和異常分派(dispatch exception)。
棧幀隨著方法調用而創建,隨著方法結束而銷毀
棧幀的存儲空間由創建它的線程分配在Java虛擬機棧之中,每一個棧幀都有自己的本地變數表、操作數棧和指向當前方法所屬的類的運行時常量池的引用。 第一個方法從調用開始到執行完成,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
本地變數表和操作數棧的容量在編譯期確定,並通過相關方法的code屬性保存及提供給棧幀使用。因此,棧幀數據結構的大小僅僅取決於Java虛擬機的實現。
對局部變數表和操作數棧的各種操作,通常都指的是對當前棧幀的局部變數表和操作數棧所進行的操作。
棧幀是線程本地私有的數據,不可能在一個棧幀之中引用另外一個線程的棧幀。
6.1 局部變數表
每個棧幀內部都包含一組稱為局部變數表的變數列表
編譯期存儲於類或介面的二進位表示之中,即通過方法的code屬性保存及提供給棧幀使用。
一個局部變數可以保存一個類型為boolean、byte、char,short、int、float、reference或returnAddress的數據。
兩個局部變數可以保存一個類型為long或double的數據。
6.2操作數棧
每個棧幀內部都包含一個稱為操作數棧的後進先出(Last-In-First-Out,LIFO)棧。
棧幀中操作數棧的最大深度由編譯期決定,並且通過方法的code屬性保存及提供給棧幀使用。
棧幀在剛剛創建時,操作數棧是空的。
Java虛擬機提供一些位元組碼指令來從局部變數表或者對象實例的欄位中複製常量或變數值到操作數棧中,也提供了一些指令用於從操作數棧
取走數據、操作數據以及把操作結果重新人棧。 在調用方法時,操作數棧也用來準備調用方法的參數以及接收方法返回結果。
例如,iadd位元組碼指令的作用是將兩個int類型的數值相加,
它要求在執行之前操作數棧的棧頂已經存在兩個由前面的其他指令所放人的int類型數值。
在執行iadd指令時,兩個int類型數值從操作棧中出棧,相加求和,然後將求和結果重新入棧。在操作數棧中,一項運算常由多個子運算( subcomputation)嵌套進行,一個子運算過程的結果可以被其他外圍運算所使用。
操作數棧的每個位置上可以保存一個Java虛擬機中定義的任意數據類型的值,包括long和double類型。
在任意時刻,操作數棧都會有一個確定的棧深度,一個long或者double類型的數據
會佔用兩個單位的棧深度,其他數據類型則會佔用一個單位的棧深度。
6.3動態鏈接
每個棧幀內部都包含一個指向當前方法所在類型的運行時常量池)的引用,以便對當前方法的代碼實現動態鏈接。
在class文件裡面,一個方法若要調用其他方法,或者訪問成員變數,則需要通過符號引用( symbolic reference)未表示,動態鏈
接的作用就是將這些以符號引用所表示的方法轉換為對實際方法的直接引用
7. 對象的表示
Java應擬機規範不強制規定對象的內部結構應當如何表示。
在Oracle的某些Java虛擬機實現中,指向對象實例的引用是一個指向句柄的指針,
這個句柄叉包含了兩個指針,其中一個指針指向一張表格,
此表格包含該對象的各個方法,還包含指向Class對象的指針,那個Class對象用來表示該對象的類型。
句柄的另外一個指針指向分配在堆中的對象實例數據。
學習Java的同學注意了!!!
學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群495273252,我們一起學Java!


※Java泛型的應用
※Java學習之static關鍵字
※Java學習之數組
※作為程序猿/媛的你在5.20這一天都幹了什麼?
※程序員求職簡歷中一些常見的問題
TAG:Java團長 |