破面試!純理論?!Java編譯到執(zhí)行的過程

面試官:今天從基礎(chǔ)先問起吧,你是怎么理解Java是一門「跨平臺」的語言,也就是「一次編譯,到處運行的」?
候選者:很好理解啊,因為我們有JVM。
候選者:Java源代碼會被編譯為class文件,class文件是運行在JVM之上的。
候選者:當我們?nèi)粘i_發(fā)安裝JDK的時候,可以發(fā)現(xiàn)JDK是分「不同的操作系統(tǒng)」,JDK里是包含JVM的,所以Java依賴著JVM實現(xiàn)了『跨平臺』
候選者:JVM是面向操作系統(tǒng)的,它負責把Class字節(jié)碼解釋成系統(tǒng)所能識別的指令并執(zhí)行,同時也負責程序運行時內(nèi)存的管理。

面試官:那要不你來聊聊從源碼文件(.java)到代碼執(zhí)行的過程唄?
候選者:嗯,沒問題的
候選者:簡單總結(jié)的話,我認為就4個步驟:編譯->加載->解釋->執(zhí)行
候選者:編譯:將源碼文件編譯成JVM可以解釋的class文件。
候選者:編譯過程會對源代碼程序做 「語法分析」「語義分析」「注解處理」等等處理,最后才生成字節(jié)碼文件。
候選者:比如對泛型的擦除和我們經(jīng)常用的Lombok就是在編譯階段干的。

候選者:加載:將編譯后的class文件加載到JVM中。
候選者:在加載階段又可以細化幾個步驟:裝載->連接->初始化
候選者:下面我對這些步驟又細說下哈。

候選者:【裝載時機】為了節(jié)省內(nèi)存的開銷,并不會一次性把所有的類都裝載至JVM,而是等到「有需要」的時候才進行裝載(比如new和反射等等)
候選者:【裝載發(fā)生】class文件是通過「類加載器」裝載到j(luò)vm中的,為了防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼,使用了雙親委派機制(它不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上)
候選者:【裝載規(guī)則】JDK 中的本地方法類一般由根加載器(Bootstrp loader)裝載,JDK 中內(nèi)部實現(xiàn)的擴展類一般由擴展加載器(ExtClassLoader )實現(xiàn)裝載,而程序中的類文件則由系統(tǒng)加載器(AppClassLoader )實現(xiàn)裝載。
候選者:裝載這個階段它做的事情可以總結(jié)為:查找并加載類的二進制數(shù)據(jù),在JVM「堆」中創(chuàng)建一個java.lang.Class類的對象,并將類相關(guān)的信息存儲在JVM「方法區(qū)」中
面試官:嗯…
候選者:通過「裝載」這個步驟后,現(xiàn)在已經(jīng)把class文件裝載到JVM中了,并創(chuàng)建出對應的Class對象以及類信息存儲至方法區(qū)了。
候選者:「連接」這個階段它做的事情可以總結(jié)為:對class的信息進行驗證、為「類變量」分配內(nèi)存空間并對其賦默認值。
候選者:連接又可以細化為幾個步驟:驗證->準備->解析
候選者:1. 驗證:驗證類是否符合 Java 規(guī)范和 JVM 規(guī)范
候選者:2. 準備:為類的靜態(tài)變量分配內(nèi)存,初始化為系統(tǒng)的初始值
候選者:3. 解析:將符號引用轉(zhuǎn)為直接引用的過程
面試官:嗯…
候選者:通過「連接」這個步驟后,現(xiàn)在已經(jīng)對class信息做校驗并分配了內(nèi)存空間和默認值了。
候選者:接下來就是「初始化」階段了,這個階段可以總結(jié)為:為類的靜態(tài)變量賦予正確的初始值。
候選者:過程大概就是收集class的靜態(tài)變量、靜態(tài)代碼塊、靜態(tài)方法至()方法,隨后從上往下開始執(zhí)行。
候選者:如果「實例化對象」則會調(diào)用方法對實例變量進行初始化,并執(zhí)行對應的構(gòu)造方法內(nèi)的代碼。

候選者:扯了這么多,現(xiàn)在其實才完成至(編譯->加載->解釋->執(zhí)行)中的加載階段,下面就來說下【解釋階段】做了什么
候選者:初始化完成之后,當我們嘗試執(zhí)行一個類的方法時,會找到對應方法的字節(jié)碼的信息,然后解釋器會把字節(jié)碼信息解釋成系統(tǒng)能識別的指令碼。
候選者:「解釋」這個階段它做的事情可以總結(jié)為:把字節(jié)碼轉(zhuǎn)換為操作系統(tǒng)識別的指令
候選者:在解釋階段會有兩種方式把字節(jié)碼信息解釋成機器指令碼,一個是字節(jié)碼解釋器、一個是即時編譯器(JIT)。

候選者:JVM會對「熱點代碼」做編譯,非熱點代碼直接進行解釋。當JVM發(fā)現(xiàn)某個方法或代碼塊的運行特別頻繁的時候,就有可能把這部分代碼認定為「熱點代碼」
候選者:使用「熱點探測」來檢測是否為熱點代碼。「熱點探測」一般有兩種方式,計數(shù)器和抽樣。HotSpot使用的是「計數(shù)器」的方式進行探測,為每個方法準備了兩類計數(shù)器:方法調(diào)用計數(shù)器和回邊計數(shù)器
候選者:這兩個計數(shù)器都有一個確定的閾值,當計數(shù)器超過閾值溢出了,就會觸發(fā)JIT編譯。
候選者:即時編譯器把熱點方法的指令碼保存起來,下次執(zhí)行的時候就無需重復的進行解釋,直接執(zhí)行緩存的機器語言
面試官:嗯…
候選者:解釋階段結(jié)束后,最后就到了執(zhí)行階段。
候選者:「執(zhí)行」這個階段它做的事情可以總結(jié)為:操作系統(tǒng)把解釋器解析出來的指令碼,調(diào)用系統(tǒng)的硬件執(zhí)行最終的程序指令。
候選者:上面就是我對從源碼文件(.java)到代碼執(zhí)行的過程的理解了。
面試官:嗯…我還想問下你剛才提到的雙親委派模型…
候選者:下次一定!

本文總結(jié):
- Java跨平臺因為有JVM屏蔽了底層操作系統(tǒng)
- Java源碼到執(zhí)行的過程,從JVM的角度看可以總結(jié)為四個步驟:編譯->加載->解釋->執(zhí)行
- 「編譯」經(jīng)過 語法分析、語義分析、注解處理 最后才生成會class文件
- 「加載」又可以細分步驟為:裝載->連接->初始化。裝載則把class文件裝載至JVM,連接則校驗class信息、分配內(nèi)存空間及賦默認值,初始化則為變量賦值為正確的初始值。連接里又可以細化為:驗證、準備、解析
- 「解釋」則是把字節(jié)碼轉(zhuǎn)換成操作系統(tǒng)可識別的執(zhí)行指令,在JVM中會有字節(jié)碼解釋器和即時編譯器。在解釋時會對代碼進行分析,查看是否為「熱點代碼」,如果為「熱點代碼」則觸發(fā)JIT編譯,下次執(zhí)行時就無需重復進行解釋,提高解釋速度
- 「執(zhí)行」調(diào)用系統(tǒng)的硬件執(zhí)行最終的程序指令
對線面試官PDF版本,可+V: java3yyy 免費領(lǐng)取