標(biāo)簽(空格分隔): JVM
運行時數(shù)據(jù)區(qū)域
整體架構(gòu)

architecture.png
模塊詳解
由所有線程共享的數(shù)據(jù)區(qū)
- Method Area(方法區(qū))
- 用于存儲已經(jīng)被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù),主要針對常量池的回收和對類型的卸載
- Non-Heap(非堆)
- 不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴展外,還可以選擇不實現(xiàn)垃圾收集
- 運行時常量池:用于存放編譯期生成的各種字面量和符號引用,這部分將在類加載后進入方法區(qū)的運行時常量池中存放
- Heap(堆)
- 唯一目的就是存放對象實例,幾乎所有的對象實例都是在這里分配內(nèi)存
- 在虛擬機啟動時創(chuàng)建
- 垃圾收集管理的主要區(qū)域
- GC堆:新生代、老年代
- 可以處在物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像磁盤空間一樣
- Execution Engine(執(zhí)行引擎)
- Native Method Interface(本地方法接口)
線程隔離的數(shù)據(jù)區(qū)
- JVM Language Stacks(JVM虛擬機棧)
- 描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程
- PC Resgisters(程序計數(shù)器)
- 較小的內(nèi)存空間
- 可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器
- 在虛擬機概念模型里(僅是概念模型),字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成
- Native Method Stacks(本地方法棧)
- 和虛擬機棧一樣,只不過是用到的native方法
其他知識點
- 棧幀中所存放的各部分信息的作用和數(shù)據(jù)結(jié)構(gòu)
- 局部變量表
- 局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量,其中存放的數(shù)據(jù)的類型是編 譯期可知的各種基本數(shù)據(jù)類型、對象引用(reference)和 returnAddress 類型(它指向了一條字節(jié)碼指令的地 址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,即在 Java 程序被編譯成 Class 文件時,就確定了所需 分配的最大局部變量表的容量。當(dāng)進入一個方法時,這個方法需要在棧中分配多大的局部變量空間是完全確定 的,在方法運行期間不會改變局部變量表的大小。
局部變量表的容量以變量槽(Slot)為最小單位。在虛擬機規(guī)范中并沒有明確指明一個 Slot 應(yīng)占用的內(nèi)存空間大 小(允許其隨著處理器、操作系統(tǒng)或虛擬機的不同而發(fā)生變化),一個 Slot 可以存放一個32位以內(nèi)的數(shù)據(jù)類 型:boolean、byte、char、short、int、float、reference 和 returnAddresss。reference 是對象的引用類
型,returnAddress 是為字節(jié)指令服務(wù)的,它執(zhí)行了一條字節(jié)碼指令的地址。對于 64 位的數(shù)據(jù)類型(long和do uble),虛擬機會以高位在前的方式為其分配兩個連續(xù)的 Slot 空間。
虛擬機通過索引定位的方式使用局部變量表,索引值的范圍是從 0 開始到局部變量表最大的 Slot 數(shù)量,對于 32 位數(shù)據(jù)類型的變量,索引 n 代表第 n 個 Slot,對于 64 位的,索引 n 代表第 n 和第 n+1 兩個 Slot。
在方法執(zhí)行時,虛擬機是使用局部變量表來完成參數(shù)值到參數(shù)變量列表的傳遞過程的,如果是實例方法(非stati c),則局部變量表中的第 0 位索引的 Slot 默認(rèn)是用于傳遞方法所屬對象實例的引用,在方法中可以通過關(guān)鍵 字“this”來訪問這個隱含的參數(shù)。其余參數(shù)則按照參數(shù)表的順序來排列,占用從1開始的局部變量 Slot,參數(shù)表 分配完畢后,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的 Slot。
局部變量表中的 Slot 是可重用的,方法體中定義的變量,作用域并不一定會覆蓋整個方法體,如果當(dāng)前字節(jié)碼P C計數(shù)器的值已經(jīng)超過了某個變量的作用域,那么這個變量對應(yīng)的 Slot 就可以交給其他變量使用。這樣的設(shè)計不 僅僅是為了節(jié)省空間,在某些情況下 Slot 的復(fù)用會直接影響到系統(tǒng)的而垃圾收集行為
- 局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量,其中存放的數(shù)據(jù)的類型是編 譯期可知的各種基本數(shù)據(jù)類型、對象引用(reference)和 returnAddress 類型(它指向了一條字節(jié)碼指令的地 址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,即在 Java 程序被編譯成 Class 文件時,就確定了所需 分配的最大局部變量表的容量。當(dāng)進入一個方法時,這個方法需要在棧中分配多大的局部變量空間是完全確定 的,在方法運行期間不會改變局部變量表的大小。
- 操作數(shù)棧
- 操作數(shù)棧又常被稱為操作棧,操作數(shù)棧的最大深度也是在編譯的時候就確定了。32 位數(shù)據(jù)類型所占的棧容量為 1,64 位數(shù)據(jù)類型所占的棧容量為 2。當(dāng)一個方法開始執(zhí)行時,它的操作棧是空的,在方法的執(zhí)行過程中,會有各 種字節(jié)碼指令(比如:加操作、賦值元算等)向操作棧中寫入和提取內(nèi)容,也就是入棧和出棧操作。
Java 虛擬機的解釋執(zhí)行引擎稱為“基于棧的執(zhí)行引擎”,其中所指的“?!本褪遣僮鲾?shù)棧。因此我們也稱 Java 虛擬機是基于棧的,這點不同于 Android 虛擬機,Android 虛擬機是基于寄存器的。
基于棧的指令集最主要的優(yōu)點是可移植性強,主要的缺點是執(zhí)行速度相對會慢些;而由于寄存器由硬件直接提
供,所以基于寄存器指令集最主要的優(yōu)點是執(zhí)行速度快,主要的缺點是可移植性差
- 操作數(shù)棧又常被稱為操作棧,操作數(shù)棧的最大深度也是在編譯的時候就確定了。32 位數(shù)據(jù)類型所占的棧容量為 1,64 位數(shù)據(jù)類型所占的棧容量為 2。當(dāng)一個方法開始執(zhí)行時,它的操作棧是空的,在方法的執(zhí)行過程中,會有各 種字節(jié)碼指令(比如:加操作、賦值元算等)向操作棧中寫入和提取內(nèi)容,也就是入棧和出棧操作。
- 動態(tài)鏈接
- 每個棧幀都包含一個指向運行時常量池(在方法區(qū)中,后面介紹)中該棧幀所屬方法的引用,持有這個引用是為 了支持方法調(diào)用過程中的動態(tài)連接。Class 文件的常量池中存在有大量的符號引用,字節(jié)碼中的方法調(diào)用指令就 以常量池中指向方法的符號引用為參數(shù)。這些符號引用,一部分會在類加載階段或第一次使用的時候轉(zhuǎn)化為直接 引用(如 final、static 域等),稱為靜態(tài)解析,另一部分將在每一次的運行期間轉(zhuǎn)化為直接引用,這部分稱為動 態(tài)連接
- 方法返回地址
- 當(dāng)一個方法被執(zhí)行后,有兩種方式退出該方法:執(zhí)行引擎遇到了任意一個方法返回的字節(jié)碼指令或遇到了異
常,并且該異常沒有在方法體內(nèi)得到處理。無論采用何種退出方式,在方法退出之后,都需要返回到方法被調(diào)用
的位置,程序才能繼續(xù)執(zhí)行。方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復(fù)它的上層方法的執(zhí)行狀
態(tài)。一般來說,方法正常退出時,調(diào)用者的 PC 計數(shù)器的值就可以作為返回地址,棧幀中很可能保存了這個計數(shù) 器值,而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。
方法退出的過程實際上等同于把當(dāng)前棧幀出站,因此退出時可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操 作數(shù)棧,如果有返回值,則把它壓入調(diào)用者棧幀的操作數(shù)棧中,調(diào)整 PC 計數(shù)器的值以指向方法調(diào)用指令后面的 一條指令
- 當(dāng)一個方法被執(zhí)行后,有兩種方式退出該方法:執(zhí)行引擎遇到了任意一個方法返回的字節(jié)碼指令或遇到了異
- 局部變量表
HotSpot對象
對象的創(chuàng)建
創(chuàng)建流程
new指令
->
檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并檢查這個符號引用代表的類是否已經(jīng)被加載、解析和初始化過;如果沒有,則進行初始化
->
虛擬機為新生對象分配內(nèi)存
->
虛擬機將分配到的內(nèi)存空間初始化為零值
->
對對象進行必要的設(shè)置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡信息
->
從虛擬機的視角來說,一個新的對象已經(jīng)產(chǎn)生了;但從Java程序視角來看,對象的創(chuàng)建才剛開始,init方法還沒有執(zhí)行,只有init方法執(zhí)行完成之后,一個真正的可用對象才算產(chǎn)生出來-
具體細(xì)節(jié)
- 內(nèi)存分配方法:
- 指針碰撞(Bump the Pointer):假設(shè)內(nèi)存是規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個指針作為分界點的指示器,那所分配的內(nèi)存就只是把那個指針向空閑空間那一邊挪動一段與對象大小相等的距離
- 空閑列表(Free List):假設(shè)內(nèi)存是不規(guī)整的
- 內(nèi)存分配方法:
對象的內(nèi)存布局
整體布局:
- 對象頭(Header)
- 實例數(shù)據(jù)(Instance Data)
- 對齊填充(Padding)
模塊詳解:
- 對象頭
- 第一部分:存儲對象自身的運行時數(shù)據(jù),如哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時間戳等
- 第二部分:類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例
- 實例數(shù)據(jù)
- 對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型字段的內(nèi)容,無論是從父類繼承的還是在子類中定義的
- 對齊填充
- 僅僅起著占位符的作用
- 因為自動內(nèi)存管理系統(tǒng)要求對象的起始地址必須是8字節(jié)的整數(shù)倍
對象的訪問定位
- 句柄訪問
- Java堆終將會劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息
- 直接指針
- reference中存儲的就是對象地址
- 比較
- 這兩種對象的訪問方式各有優(yōu)勢,使用句柄訪問方式的最大好處就是 reference 中存放的是穩(wěn)定的句柄地址,在 對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數(shù)據(jù)指針,而 reference 本身不 需要修改。使用直接指針訪問方式的最大好處是速度快,它節(jié)省了一次指針定位的時間開銷。目前 Java 默認(rèn)使 用的 HotSpot 虛擬機采用的便是是第二種方式進行對象訪問的。
-
圖片
handle.png

direct.png
