本篇博客主要針對Java虛擬機的類加載機制郭赐,虛擬機字節(jié)碼執(zhí)行引擎,早期編譯優(yōu)化進行總結(jié),其余部分總結(jié)請點擊Java虛擬總結(jié)上篇 萧落。
一.虛擬機類加載機制
概述
虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存嚎幸,并對數(shù)據(jù)進行校驗颜矿、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機直接使用的Java類型嫉晶,這就是虛擬機的類加載機制骑疆。
類加載的時機
類加載的時機不止一種:
- 遇到new等字節(jié)碼指令時會進行類加載
- 反射調(diào)用時會進行類加載
在初始化時,若待初始化的類有父類則其父類先進行初始化(接口除外),并且先初始化包含main的主類替废。需要注意的是子類引用父類非final靜態(tài)變量時箍铭,只初始化靜態(tài)變量所在類,即父類椎镣,而引用final類型static變量不會引起任何初始化诈火,因為其編譯期間就已經(jīng)儲存在常量池中了。另外數(shù)組定義也是不會引發(fā)類的初始化状答。比如
Student[] stus=new Student[10];
是不會引起Student類的初始化的冷守。
類加載的過程
加載過程
通過類的全限定名來獲取定義此類的二進制字節(jié)流,將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)惊科,在內(nèi)存中生成一個代表類的數(shù)據(jù)訪問入口的java.lang.Class對象拍摇。
驗證過程
驗證過程的目的是為了確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全馆截。主要有
- 文件格式驗證:驗證魔數(shù)充活,主次版本號,常量類型等蜡娶。
- 元數(shù)據(jù)驗證:是否有父類混卵,是否繼承了不該繼承的類,抽象類是否實現(xiàn)了方法等窖张。
- 字節(jié)碼驗證:確保程序語義是合法的幕随,符合邏輯的。如類型轉(zhuǎn)換荤堪,跳轉(zhuǎn)指令等合陵。
- 符號引用驗證:對類自身以外的信息(常量池中的各種引用)進行匹配校驗。
準備過程
正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,只包括類變量而不包括實例變量和final類變量澄阳,而且僅僅只是初始化為0值拥知。
解析過程
虛擬機將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。符號引用用一組符號來描述所引用的目標碎赢。而直接引用是直接指向目標的指針低剔,相對偏移量或是一個能間接定位到目標的句柄。
初始化階段
在初始化階段真正開始執(zhí)行Java程序代碼(字節(jié)碼),執(zhí)行類的構(gòu)造器<clinit>()方法,<clinit>()方法是由編譯器自動收集所有類變量的賦值動作和靜態(tài)語句塊的語句合并而成,同一類中的靜態(tài)塊與類變量按順序初始化,在同一個加載器下襟齿,一個類只會被初始化一次姻锁。
類加載器
實現(xiàn)通過一個類的全限定名獲取描述此類的二進制字節(jié)流的代碼模塊稱為類加載器。比較兩個類是否相等猜欺,一定是在同一個類加載器的前提下進行的位隶,否則哪怕Class文件都一樣也不相等
類加載器的分類
- 啟動類加載器, 負責將存放在
<JAVA_HOME>\lib
目錄或-Xbootclasspath
參數(shù)指定的路徑中的類庫加載到內(nèi)存中开皿。 - 擴展類加載器涧黄,負責加載
<JAVA_HOME>\lib\ext
目錄或java.ext.dirs
系統(tǒng)變量指定的路徑中的所有類庫。 - 應(yīng)用程序類加載器赋荆,負責加載用戶類路徑(
classpath
)上的指定類庫笋妥,我們可以直接使用這個類加載器。一般情況窄潭,如果我們沒有自定義類加載器默認就是用這個加載器春宣。
雙親委派模型
雙親委派模型工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類嫉你,而是把這個請求委派給父類加載器完成月帝。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內(nèi)找不到指定的類時(即ClassNotFoundException
)均抽,子加載器才會嘗試自己去加載嫁赏。
這樣做的好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。
二.虛擬機字節(jié)碼執(zhí)行引擎
虛擬機的執(zhí)行引擎自行實現(xiàn)油挥,可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系。
棧幀
棧幀是用于支持虛擬機進行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)款熬,是虛擬機棧的棧元素深寥。它儲存了方法的局部變量表,操作數(shù)棧贤牛,動態(tài)鏈接惋鹅,方法返回地址,對于活動線程來說殉簸,只有棧頂?shù)臈攀怯行У娜蚣Q為當前棧幀,與其關(guān)聯(lián)的方法叫做當前方法般卑。
局部變量表
局部變量表存放方法參數(shù)和方法內(nèi)部定義的變量武鲁。單位是slot(槽),最大可以達到32位蝠检。垃圾回收時沐鼠,slot可以復(fù)用,將不使用的變量置為null是有意義的,方便垃圾回收饲梭。局部變量不像類變量乘盖,是沒有初始值的。
JIT編譯器
當虛擬機發(fā)現(xiàn)某個方法或代碼塊運行特別頻繁時憔涉,就會把這些代碼認定為 “Hot Spot Code”(熱點代碼)订框,為了提高熱點代碼的執(zhí)行效率,在運行時兜叨,虛擬機將會把這些代碼編譯成與本地平臺相關(guān)的機器碼穿扳,并進行各層次的優(yōu)化,完成這項任務(wù)的正是 JIT 編譯器浪腐。
方法返回地址
- 遇到方法的返回指令-->正常完成出口
- 遇到異常并且未處理-->異常完成出口纵揍,不會給上層調(diào)用者產(chǎn)生任何返回值
方法調(diào)用
方法在編譯時并不確定方法的真實地址,而是一個符號引用议街,使得Java的動態(tài)擴展能力提升泽谨,在類加載過程甚至運行時才確定目標方法的直接引用。
解析
在類的解析階段將一部分符號引用轉(zhuǎn)換為直接引用特漩,這部分符號引用代表的方法必須“編譯期可知吧雹,運行時不變”,如靜態(tài)方法涂身,私有方法雄卷,實例構(gòu)造器,父類方法蛤售。final方法也是丁鹉。
分派
靜態(tài)分派(與重載相關(guān)),依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作悴能。自動轉(zhuǎn)型順序:char->int->long->float->double->Character->Serializable->Object->char...
動態(tài)分派(重寫相關(guān))揣钦,找到操作數(shù)棧頂?shù)牡谝粋€元素所指向的對象的實際類型,若常量池中的描述符和簡單名稱都相符漠酿,則返回直接引用冯凹,否則對其父類進行第二步。
動態(tài)分配的實現(xiàn):
在類的方法區(qū)建立一個虛方法表提升效率炒嘲,若子類未重寫父類的方法宇姚,則子類的繼承方法中地址和父類方法的地址是一樣的,若重寫了父類的方法夫凸,則子類的方法地址就會改變浑劳,指向自己實現(xiàn)的版本。如上圖Son的clone方法沒有被重寫寸痢,指向的是Object父類的地址呀洲,而hardChoice方法被重寫了,指向的是Son自己實現(xiàn)的地址。
動態(tài)類型語言
類型檢查的主題過程在運行期而不是在編譯期道逗,如Python兵罢,Javascript,Ruby滓窍,PHP卖词,與之相對的就是靜態(tài)語言。
解釋執(zhí)行與編譯執(zhí)行
解釋執(zhí)行為邊解釋邊執(zhí)行吏夯,編譯執(zhí)行則是先將源代碼編譯成目標語言 (如: 機器語言) 之后通過連接程序連接到生成的目標程序進行執(zhí)行此蜈。
基于棧的字節(jié)碼解釋執(zhí)行引擎
- 基于棧的指令集:Java編譯器輸出的指令流
- 基于寄存器的指令集:x86匯編
三.早期編譯器優(yōu)化
編譯器
三種編譯器:
- 前端編譯器:把.java變成.class的過程,eg:Javac
- 后端運行期編譯器(JIT):把字節(jié)碼變成機器碼的過程,eg:Hotpot的C1噪生,C2編譯器
- 靜態(tài)提前編譯器(AOT):直接把*.java變成機器碼的過程裆赵,eg:GCJ(GNU Compiler for the Java)
解析與填充符號表
詞法分析
標記是編譯過程的最小元素,關(guān)鍵字跺嗽、變量名战授、字面量、運算符都可以成為標記桨嫁,詞法分析就是將源代碼的字符流轉(zhuǎn)變?yōu)闃擞浖稀?/p>
語法分析
語法分析是根據(jù)Token序列構(gòu)造抽象語法樹的過程植兰。抽象語法樹是用來描述程序代碼語法結(jié)構(gòu)的樹形表示方法,每一個節(jié)點都代表著程序代碼的一個語法結(jié)構(gòu):包璃吧,類型楣导,修飾符等。
注解處理器
類似編譯器的一種插件畜挨,如果插件對語法樹進行了修改筒繁,編譯器將回到解析及填充符號表的過程重新處理。
語義分析
對語法抽象樹進行上下文有關(guān)性質(zhì)的審查巴元,如類型檢查膝晾。
字節(jié)碼生成
將前面各個步驟生成的信息轉(zhuǎn)換成字節(jié)碼寫到磁盤中,類構(gòu)造器<cinit>和實例構(gòu)造器<init>就是在這個階段添加到語法樹中务冕。
Java語法糖
- 泛型與類型擦除:與C#不一樣,Java的泛型是偽泛型幻赚,在生成的字節(jié)碼中已經(jīng)被替換成了原生類型了禀忆,會自動加上類型轉(zhuǎn)換。
- 遍歷:自動轉(zhuǎn)換為iterator遍歷落恼。
- 裝箱與拆箱:==運算在不遇到算數(shù)運算的情況下不會自動拆箱箩退。equals方法不會處理數(shù)據(jù)的類型轉(zhuǎn)換,而==會佳谦。
條件編譯
編譯器不會編譯if到達不到的語句戴涝,也就是取消分支不成立的代碼塊,可以查看反編譯后的代碼驗證條件編譯。