摘要:驗(yàn)證過程驗(yàn)證過程的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。二虛擬機(jī)字節(jié)碼執(zhí)行引擎虛擬機(jī)的執(zhí)行引擎自行實(shí)現(xiàn),可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系。
本篇博客主要針對Java虛擬機(jī)的類加載機(jī)制,虛擬機(jī)字節(jié)碼執(zhí)行引擎,早期編譯優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請點(diǎn)擊Java虛擬總結(jié)上篇 。
一.虛擬機(jī)類加載機(jī)制 概述虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。
類加載的時機(jī)類加載的時機(jī)不止一種:
遇到new等字節(jié)碼指令時會進(jìn)行類加載
反射調(diào)用時會進(jìn)行類加載
在初始化時,若待初始化的類有父類則其父類先進(jìn)行初始化(接口除外),并且先初始化包含main的主類。需要注意的是子類引用父類非final靜態(tài)變量時,只初始化靜態(tài)變量所在類,即父類,而引用final類型static變量不會引起任何初始化,因?yàn)槠渚幾g期間就已經(jīng)儲存在常量池中了。另外數(shù)組定義也是不會引發(fā)類的初始化。比如
Student[] stus=new Student[10];
是不會引起Student類的初始化的。
類加載的過程 加載過程通過類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流,將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中生成一個代表類的數(shù)據(jù)訪問入口的java.lang.Class對象。
驗(yàn)證過程驗(yàn)證過程的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。主要有
文件格式驗(yàn)證:驗(yàn)證魔數(shù),主次版本號,常量類型等。
元數(shù)據(jù)驗(yàn)證:是否有父類,是否繼承了不該繼承的類,抽象類是否實(shí)現(xiàn)了方法等。
字節(jié)碼驗(yàn)證:確保程序語義是合法的,符合邏輯的。如類型轉(zhuǎn)換,跳轉(zhuǎn)指令等。
符號引用驗(yàn)證:對類自身以外的信息(常量池中的各種引用)進(jìn)行匹配校驗(yàn)。
準(zhǔn)備過程正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,只包括類變量而不包括實(shí)例變量和final類變量,而且僅僅只是初始化為0值。
解析過程虛擬機(jī)將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。符號引用用一組符號來描述所引用的目標(biāo)。而直接引用是直接指向目標(biāo)的指針,相對偏移量或是一個能間接定位到目標(biāo)的句柄。
初始化階段在初始化階段真正開始執(zhí)行Java程序代碼(字節(jié)碼),執(zhí)行類的構(gòu)造器
實(shí)現(xiàn)通過一個類的全限定名獲取描述此類的二進(jìn)制字節(jié)流的代碼模塊稱為類加載器。比較兩個類是否相等,一定是在同一個類加載器的前提下進(jìn)行的,否則哪怕Class文件都一樣也不相等
類加載器的分類啟動類加載器, 負(fù)責(zé)將存放在
擴(kuò)展類加載器,負(fù)責(zé)加載
應(yīng)用程序類加載器,負(fù)責(zé)加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認(rèn)就是用這個加載器。
雙親委派模型雙親委派模型工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當(dāng)父加載器在自己的搜索范圍內(nèi)找不到指定的類時(即ClassNotFoundException),子加載器才會嘗試自己去加載。
這樣做的好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。
二.虛擬機(jī)字節(jié)碼執(zhí)行引擎虛擬機(jī)的執(zhí)行引擎自行實(shí)現(xiàn),可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系。
棧幀棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),是虛擬機(jī)棧的棧元素。它儲存了方法的局部變量表,操作數(shù)棧,動態(tài)鏈接,方法返回地址,對于活動線程來說,只有棧頂?shù)臈攀怯行У?,稱為當(dāng)前棧幀,與其關(guān)聯(lián)的方法叫做當(dāng)前方法。
局部變量表局部變量表存放方法參數(shù)和方法內(nèi)部定義的變量。單位是slot(槽),最大可以達(dá)到32位。垃圾回收時,slot可以復(fù)用,將不使用的變量置為null是有意義的,方便垃圾回收。局部變量不像類變量,是沒有初始值的。
JIT編譯器當(dāng)虛擬機(jī)發(fā)現(xiàn)某個方法或代碼塊運(yùn)行特別頻繁時,就會把這些代碼認(rèn)定為 “Hot Spot Code”(熱點(diǎn)代碼),為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時,虛擬機(jī)將會把這些代碼編譯成與本地平臺相關(guān)的機(jī)器碼,并進(jìn)行各層次的優(yōu)化,完成這項(xiàng)任務(wù)的正是 JIT 編譯器。
方法返回地址遇到方法的返回指令-->正常完成出口
遇到異常并且未處理-->異常完成出口,不會給上層調(diào)用者產(chǎn)生任何返回值
方法調(diào)用方法在編譯時并不確定方法的真實(shí)地址,而是一個符號引用,使得Java的動態(tài)擴(kuò)展能力提升,在類加載過程甚至運(yùn)行時才確定目標(biāo)方法的直接引用。
解析在類的解析階段將一部分符號引用轉(zhuǎn)換為直接引用,這部分符號引用代表的方法必須“編譯期可知,運(yùn)行時不變”,如靜態(tài)方法,私有方法,實(shí)例構(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ù)牡谝粋€元素所指向的對象的實(shí)際類型,若常量池中的描述符和簡單名稱都相符,則返回直接引用,否則對其父類進(jìn)行第二步。
動態(tài)分配的實(shí)現(xiàn):
在類的方法區(qū)建立一個虛方法表提升效率,若子類未重寫父類的方法,則子類的繼承方法中地址和父類方法的地址是一樣的,若重寫了父類的方法,則子類的方法地址就會改變,指向自己實(shí)現(xiàn)的版本。如上圖Son的clone方法沒有被重寫,指向的是Object父類的地址,而hardChoice方法被重寫了,指向的是Son自己實(shí)現(xiàn)的地址。
動態(tài)類型語言類型檢查的主題過程在運(yùn)行期而不是在編譯期,如Python,Javascript,Ruby,PHP,與之相對的就是靜態(tài)語言。
解釋執(zhí)行與編譯執(zhí)行解釋執(zhí)行為邊解釋邊執(zhí)行,編譯執(zhí)行則是先將源代碼編譯成目標(biāo)語言 (如: 機(jī)器語言) 之后通過連接程序連接到生成的目標(biāo)程序進(jìn)行執(zhí)行。
基于棧的字節(jié)碼解釋執(zhí)行引擎基于棧的指令集:Java編譯器輸出的指令流
基于寄存器的指令集:x86匯編
三.早期編譯器優(yōu)化 編譯器三種編譯器:
前端編譯器:把.java變成.class的過程,eg:Javac
后端運(yùn)行期編譯器(JIT):把字節(jié)碼變成機(jī)器碼的過程,eg:Hotpot的C1,C2編譯器
靜態(tài)提前編譯器(AOT):直接把*.java變成機(jī)器碼的過程,eg:GCJ(GNU Compiler for the Java)
解析與填充符號表 詞法分析標(biāo)記是編譯過程的最小元素,關(guān)鍵字、變量名、字面量、運(yùn)算符都可以成為標(biāo)記,詞法分析就是將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記集合。
語法分析語法分析是根據(jù)Token序列構(gòu)造抽象語法樹的過程。抽象語法樹是用來描述程序代碼語法結(jié)構(gòu)的樹形表示方法,每一個節(jié)點(diǎn)都代表著程序代碼的一個語法結(jié)構(gòu):包,類型,修飾符等。
注解處理器類似編譯器的一種插件,如果插件對語法樹進(jìn)行了修改,編譯器將回到解析及填充符號表的過程重新處理。
語義分析對語法抽象樹進(jìn)行上下文有關(guān)性質(zhì)的審查,如類型檢查。
字節(jié)碼生成將前面各個步驟生成的信息轉(zhuǎn)換成字節(jié)碼寫到磁盤中,類構(gòu)造器
泛型與類型擦除:與C#不一樣,Java的泛型是偽泛型,在生成的字節(jié)碼中已經(jīng)被替換成了原生類型了,會自動加上類型轉(zhuǎn)換。
遍歷:自動轉(zhuǎn)換為iterator遍歷。
裝箱與拆箱:==運(yùn)算在不遇到算數(shù)運(yùn)算的情況下不會自動拆箱。equals方法不會處理數(shù)據(jù)的類型轉(zhuǎn)換,而==會。
條件編譯編譯器不會編譯if到達(dá)不到的語句,也就是取消分支不成立的代碼塊,可以查看反編譯后的代碼驗(yàn)證條件編譯。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/68645.html
摘要:于是這篇博客就針對虛擬機(jī)的各個知識點(diǎn)進(jìn)行歸納。若虛擬機(jī)棧請求擴(kuò)展時無法申請到足夠的內(nèi)存,則拋出異常。類索引用于確定類的全限定名,父類索引用于確定父類的全限定名。字節(jié)碼指令操作碼長度為一個字節(jié),所以總數(shù)最多不超過條。 Java虛擬機(jī)一直是Java的重難點(diǎn),一方面由于系統(tǒng)封裝得太好,你平常寫程序的時候幾乎感覺不到它的存在,另一方面了解必要的Java虛擬機(jī)工作原理才能對真實(shí)工作環(huán)境下的bug...
摘要:本篇博客主要針對虛擬機(jī)的晚期編譯優(yōu)化,內(nèi)存模型與線程,線程安全與鎖優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請點(diǎn)擊虛擬總結(jié)上篇,虛擬機(jī)總結(jié)中篇。 本篇博客主要針對Java虛擬機(jī)的晚期編譯優(yōu)化,Java內(nèi)存模型與線程,線程安全與鎖優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請點(diǎn)擊Java虛擬總結(jié)上篇 ,Java虛擬機(jī)總結(jié)中篇。 一.晚期運(yùn)行期優(yōu)化 即時編譯器JIT 即時編譯器JIT的作用就是熱點(diǎn)代碼轉(zhuǎn)換為平臺相關(guān)的機(jī)器碼...
摘要:與都繼承自類,在中也是使用字符數(shù)組保存字符串,,這兩種對象都是可變的。采用字節(jié)碼的好處語言通過字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時又保留了解釋型語言可移植的特點(diǎn)。 String和StringBuffer、StringBuilder的區(qū)別是什么?String為什么是不可變的? String和StringBuffer、StringBuilder的區(qū)別 可變性...
摘要:最近在備戰(zhàn)面試的過程中,整理一下面試題。成員變量如果沒有被賦初值,則會自動以類型的默認(rèn)值而賦值一種情況例外被修飾但沒有被修飾的成員變量必須顯示地賦值而局部變量則不會自動賦值。 最近在備戰(zhàn)面試的過程中,整理一下面試題。大多數(shù)題目都是自己手敲的,網(wǎng)上也有很多這樣的總結(jié)。自己感覺總是很亂,所以花了很久把自己覺得重要的東西總結(jié)了一下。 面向?qū)ο蠛兔嫦蜻^程的區(qū)別 面向過程: 優(yōu)點(diǎn):性能比面...
閱讀 3162·2021-11-24 10:47
閱讀 3927·2021-11-02 14:43
閱讀 2320·2021-09-26 10:15
閱讀 2685·2021-09-08 09:35
閱讀 640·2019-08-30 12:45
閱讀 2839·2019-08-29 17:04
閱讀 3338·2019-08-26 14:05
閱讀 1364·2019-08-26 12:10