java代码从编译到加载执行的过程
代碼編譯
在剛接觸java時(shí),我們都知道通過(guò)javac命令將java源碼文件編譯成.class字節(jié)碼文件,這是由編譯器來(lái)完成的。
包括泛型擦除屬于編譯時(shí)期的語(yǔ)法糖。
.class字節(jié)碼文件是跨平臺(tái)的,也就是不管什么平臺(tái)的JVM,都可以加載執(zhí)行。使用javap -c X.class 可以將字節(jié)碼文件轉(zhuǎn)為可讀性較好的格式
類(lèi)什么時(shí)候加載
JVM并不是把所有的字節(jié)碼文件一下子全加載進(jìn)內(nèi)存,Java類(lèi)的加載是動(dòng)態(tài)的,它并不會(huì)一次性將所有類(lèi)全部加載后再運(yùn)行,而是保證程序運(yùn)行的基礎(chǔ)類(lèi)(像是基類(lèi))完全加載到j(luò)vm中,至于其他類(lèi),則在需要的時(shí)候才加載。這當(dāng)然就是為了節(jié)省內(nèi)存開(kāi)銷(xiāo)。
虛擬機(jī)規(guī)范則是嚴(yán)格規(guī)定了有且只有5種情況必須立即對(duì)類(lèi)進(jìn)行“初始化”(class文件加載到JVM中):
- 創(chuàng)建類(lèi)的實(shí)例(new 的方式)。訪問(wèn)某個(gè)類(lèi)或接口的靜態(tài)變量,或者對(duì)該靜態(tài)變量賦值,調(diào)用類(lèi)的靜態(tài)方法
- 反射的方式
- 初始化某個(gè)類(lèi)的子類(lèi),則其父類(lèi)也會(huì)被初始化
- Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類(lèi)的類(lèi),直接使用java.exe命令來(lái)運(yùn)行某個(gè)主類(lèi)(包含main方法的那個(gè)類(lèi))
- 當(dāng)使用JDK1.7的動(dòng)態(tài)語(yǔ)言支持時(shí)(…)
如何將類(lèi)加載進(jìn)JVM
class文件是通過(guò)類(lèi)的加載器裝載到j(luò)vm中的!
Java默認(rèn)有三種類(lèi)加載器:
各個(gè)加載器的工作責(zé)任:
- 1)啟動(dòng)類(lèi)加載器Bootstrap ClassLoader:負(fù)責(zé)加載$JAVA_HOME中jre/lib/rt.jar里所有的class。由C++實(shí)現(xiàn),不是ClassLoader子類(lèi)
- 2)擴(kuò)展類(lèi)加載器Extension ClassLoader:負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-Djava.ext.dirs指定目錄下的jar包
- 3)應(yīng)用類(lèi)加載器App ClassLoader:負(fù)責(zé)記載classpath中指定的jar包及目錄中class
工作過(guò)程:
- 1、當(dāng)AppClassLoader加載一個(gè)class時(shí),它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把類(lèi)加載請(qǐng)求委派給父類(lèi)加載器ExtClassLoader去完成。
- 2、當(dāng)ExtClassLoader加載一個(gè)class時(shí),它首先也不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把類(lèi)加載請(qǐng)求委派給BootStrapClassLoader去完成。
- 3、如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會(huì)使用ExtClassLoader來(lái)嘗試加載;
- 4、若ExtClassLoader也加載失敗,則會(huì)使用AppClassLoader來(lái)加載
- 5、如果AppClassLoader也加載失敗,則會(huì)報(bào)出異常ClassNotFoundException
其實(shí)這就是所謂的雙親委派模型。簡(jiǎn)單來(lái)說(shuō):如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把請(qǐng)求委托給父加載器去完成,依次向上。
好處:
防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼(安全性角度),比如你不可能自定義一個(gè)String類(lèi)讓JVM加載。
特別說(shuō)明:
類(lèi)加載器在成功加載某個(gè)類(lèi)之后,會(huì)把得到的 java.lang.Class類(lèi)的實(shí)例緩存起來(lái)。下次再請(qǐng)求加載該類(lèi)的時(shí)候,類(lèi)加載器會(huì)直接使用緩存的類(lèi)的實(shí)例,而不會(huì)嘗試再次加載。
自定義類(lèi)加載器可以實(shí)現(xiàn)資源jar隔離,代碼保護(hù)、熱加載等,具體查看自定義類(lèi)加載器的實(shí)際應(yīng)用場(chǎng)景、類(lèi)加載器詳解
類(lèi)加載的詳細(xì)過(guò)程
加載器加載到j(luò)vm中,接下來(lái)其實(shí)又分了好幾個(gè)步驟:
加載,查找并加載類(lèi)的二進(jìn)制數(shù)據(jù),在Java堆中也創(chuàng)建一個(gè)java.lang.Class類(lèi)的對(duì)象。
連接,連接又包含三塊內(nèi)容:驗(yàn)證、準(zhǔn)備、初始化。
1)驗(yàn)證,文件格式、元數(shù)據(jù)、字節(jié)碼、符號(hào)引用驗(yàn)證;
2)準(zhǔn)備,為類(lèi)的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值;
3)解析,把類(lèi)中的符號(hào)引用轉(zhuǎn)換為直接引用
初始化,為類(lèi)的靜態(tài)變量賦予正確的初始值。
在類(lèi)加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。
JIT即時(shí)編譯
但是不同于 C / C++ 語(yǔ)言直接被翻譯成機(jī)器指令,java字節(jié)碼文件,它是經(jīng)過(guò)編譯器預(yù)處理過(guò)的一種文件,它本身是二進(jìn)制文件,但是不可以被系統(tǒng)直接執(zhí)行,需要虛擬機(jī)解釋執(zhí)行,即逐條取出逐條執(zhí)行。很顯然經(jīng)過(guò)解釋執(zhí)行,其執(zhí)行速度必然會(huì)比可執(zhí)行的二進(jìn)制字節(jié)碼程序慢很多。為了提高執(zhí)行速度,引入了 JIT 技術(shù)。
JIT 技術(shù)就是把這些Java字節(jié)碼重新編譯優(yōu)化,生成機(jī)器碼,讓CPU直接執(zhí)行。這樣編出來(lái)的代碼效率會(huì)更高。在運(yùn)行時(shí) JIT 會(huì)把翻譯過(guò)的機(jī)器碼保存起來(lái),以備下次使用。這種在運(yùn)行時(shí)按需編譯的方式就是Just In Time。
通常,我們不必把所有的Java方法都編譯成機(jī)器碼,只需要把調(diào)用最頻繁,占據(jù)CPU時(shí)間最長(zhǎng)的方法找出來(lái)將其編譯成機(jī)器碼。這種調(diào)用最頻繁的Java方法就是我們常說(shuō)的熱點(diǎn)方法。熱點(diǎn)代碼如多次調(diào)用的方法、多次執(zhí)行的循環(huán)體。
使用熱點(diǎn)探測(cè)來(lái)檢測(cè)是否為熱點(diǎn)代碼,熱點(diǎn)探測(cè)有兩種方式:采樣、計(jì)數(shù)器。目前HotSpot使用的是計(jì)數(shù)器的方式,當(dāng)計(jì)數(shù)器超過(guò)閾值溢出了,就會(huì)觸發(fā)JIT編譯。
JVM之JIT原理
IBM講JIT
總結(jié)
以上是生活随笔為你收集整理的java代码从编译到加载执行的过程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: synchronized的底层原理
- 下一篇: (三)栈