Java編譯原理
Java 虛擬機(jī)(JVM)是可運(yùn)行Java 代碼的假想計(jì)算機(jī)。只要根據(jù)JVM規(guī)格描述將解釋器移植到特定的計(jì)算機(jī)上,就能保證經(jīng)過(guò)編譯的任何Java代碼能夠在該系統(tǒng)上運(yùn)行。
一、Java源文件的編譯、下載 、解釋和執(zhí)行
Java應(yīng)用程序的開(kāi)發(fā)周期包括編譯、下載 、解釋和執(zhí)行幾個(gè)部分。Java編譯程序?qū)ava源程序翻譯為JVM可執(zhí)行代碼?字節(jié)碼。這一編譯過(guò)程同C/C++ 的編譯有些不同。當(dāng)C編譯器編譯生成一個(gè)對(duì)象的代碼時(shí),該代碼是為在某一特定硬件平臺(tái)運(yùn)行而產(chǎn)生的。因此,在編譯過(guò)程中,編譯程序通過(guò)查表將所有對(duì)符號(hào)的引用轉(zhuǎn)換為特定的內(nèi)存偏移量,以保證程序運(yùn)行。Java編譯器卻不將對(duì)變量和方法的引用編譯為數(shù)值引用,也不確定程序執(zhí)行過(guò)程中的內(nèi)存布局,而是將這些符號(hào)引用信息保留在字節(jié)碼中,由解釋器在運(yùn)行過(guò)程中創(chuàng)立內(nèi)存布局,然后再通過(guò)查表來(lái)確定一個(gè)方法所在的地址。這樣就有效的保證了Java的可移植性和安全 性。
運(yùn)行JVM字節(jié)碼的工作是由解釋器來(lái)完成的。解釋執(zhí)行過(guò)程分三部進(jìn)行:代碼的裝入、代碼的校驗(yàn)和代碼的執(zhí)行。裝入代碼的工作由“類(lèi)裝載器”(class loader)完成。類(lèi)裝載器負(fù)責(zé)裝入運(yùn)行一個(gè)程序需要的所有代碼,這也包括程序代碼中的類(lèi)所繼承的類(lèi)和被其調(diào)用的類(lèi)。當(dāng)類(lèi)裝載器裝入一個(gè)類(lèi)時(shí),該類(lèi)被放在自己的名字空間中。除了通過(guò)符號(hào)引用自己名字空間以外的類(lèi),類(lèi)之間沒(méi)有其他辦法可以影響其他類(lèi)。在本臺(tái)計(jì)算機(jī)上的所有類(lèi)都在同一地址空間內(nèi),而所有從外部引進(jìn)的類(lèi),都有一個(gè)自己獨(dú)立的名字空間。這使得本地類(lèi)通過(guò)共享相同的名字空間獲得較高的運(yùn)行效率,同時(shí)又保證它們與從外部引進(jìn)的類(lèi)不會(huì)相互影響。當(dāng)裝入了運(yùn)行程序需要的所有類(lèi)后,解釋器便可確定整個(gè)可執(zhí)行程序的內(nèi)存布局。解釋器為符號(hào)引用同特定的地址空間建立對(duì)應(yīng)關(guān)系及查詢(xún)表。通過(guò)在這一階段確定代碼的內(nèi)存布局,Java很好地解決了由超類(lèi)改變而使子類(lèi)崩潰的問(wèn)題,同時(shí)也防止了代碼對(duì)地址的非法訪問(wèn)。
隨后,被裝入的代碼由字節(jié)碼校驗(yàn)器進(jìn)行檢查。校驗(yàn)器可發(fā)現(xiàn)操作數(shù)棧溢出,非法數(shù)據(jù)類(lèi)型轉(zhuǎn)化等多種錯(cuò)誤。通過(guò)校驗(yàn)后,代碼便開(kāi)始執(zhí)行了。
二、Java字節(jié)碼的執(zhí)行有兩種方式:
1、即時(shí)編譯方式:解釋器先將字節(jié)碼編譯成機(jī)器碼,然后再執(zhí)行該機(jī)器碼。
2、解釋執(zhí)行方式:解釋器通過(guò)每次解釋并執(zhí)行一小段代碼來(lái)完成Java字節(jié)碼程 序的所有操作。
通常采用的是第二種方法。由于JVM規(guī)格描述具有足夠的靈活性,這使得將字節(jié)碼翻譯為機(jī)器代碼的工作
具有較高的效率。對(duì)于那些對(duì)運(yùn)行速度要求較高的應(yīng)用程序,解釋器可將Java字節(jié)碼即時(shí)編譯為機(jī)器碼,從而很好地保證了Java代碼的可移植性和高性能。
Java程序編譯和運(yùn)行的過(guò)程
Java整個(gè)編譯以及運(yùn)行的過(guò)程相當(dāng)繁瑣,本文通過(guò)一個(gè)簡(jiǎn)單的程序來(lái)簡(jiǎn)單的說(shuō)明整個(gè)流程。
如下圖,Java程序從源文件創(chuàng)建到程序運(yùn)行要經(jīng)過(guò)兩大步驟:
1、源文件由編譯器編譯成字節(jié)碼(ByteCode)
2、字節(jié)碼由java虛擬機(jī)解釋運(yùn)行。因?yàn)閖ava程序既要編譯同時(shí)也要經(jīng)過(guò)JVM的解釋運(yùn)行,所以說(shuō)Java被稱(chēng)為半解釋語(yǔ)言( “semi-interpreted” language)。
下面通過(guò)以下這個(gè)java程序,來(lái)說(shuō)明java程序從編譯到最后運(yùn)行的整個(gè)流程。代碼如下:
第一步(編譯): 創(chuàng)建完源文件之后,程序會(huì)先被編譯為.class文件。Java編譯一個(gè)類(lèi)時(shí),如果這個(gè)類(lèi)所依賴(lài)的類(lèi)還沒(méi)有被編譯,編譯器就會(huì)先編譯這個(gè)被依賴(lài)的類(lèi),然后引用,否則直接引用,這個(gè)有點(diǎn)象make。如果java編譯器在指定目錄下找不到該類(lèi)所其依賴(lài)的類(lèi)的.class文件或者.java源文件的話,編譯器話報(bào)“cant find symbol”的錯(cuò)誤。
編譯后的字節(jié)碼文件格式主要分為兩部分:常量池和方法字節(jié)碼。常量池記錄的是代碼出現(xiàn)過(guò)的所有token(類(lèi)名,成員變量名等等)以及符號(hào)引用(方法引用,成員變量引用等等);方法字節(jié)碼放的是類(lèi)中各個(gè)方法的字節(jié)碼。下面是MainApp.class通過(guò)反匯編的結(jié)果,我們可以清楚看到.class文件的結(jié)構(gòu):
第二步(運(yùn)行):java類(lèi)運(yùn)行的過(guò)程大概可分為兩個(gè)過(guò)程:1、類(lèi)的加載 2、類(lèi)的執(zhí)行。需要說(shuō)明的是:JVM主要在程序第一次主動(dòng)使用類(lèi)的時(shí)候,才會(huì)去加載該類(lèi)。也就是說(shuō),JVM并不是在一開(kāi)始就把一個(gè)程序就所有的類(lèi)都加載到內(nèi)存中,而是到不得不用的時(shí)候才把它加載進(jìn)來(lái),而且只加載一次。
下面是程序運(yùn)行的詳細(xì)步驟:
1、在編譯好java程序得到MainApp.class文件后,在命令行上敲java AppMain。系統(tǒng)就會(huì)啟動(dòng)一個(gè)jvm進(jìn)程,jvm進(jìn)程從classpath路徑中找到一個(gè)名為AppMain.class的二進(jìn)制文件,將MainApp的類(lèi)信息加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),這個(gè)過(guò)程叫做MainApp類(lèi)的加載。
2、然后JVM找到AppMain的主函數(shù)入口,開(kāi)始執(zhí)行main函數(shù)。
3、main函數(shù)的第一條命令是Animal animal = new Animal(“Puppy”);就是讓JVM創(chuàng)建一個(gè)Animal對(duì)象,但是這時(shí)候方法區(qū)中沒(méi)有Animal類(lèi)的信息,所以JVM馬上加載Animal類(lèi),把Animal類(lèi)的類(lèi)型信息放到方法區(qū)中。
4、加載完Animal類(lèi)之后,Java虛擬機(jī)做的第一件事情就是在堆區(qū)中為一個(gè)新的Animal實(shí)例分配內(nèi)存, 然后調(diào)用構(gòu)造函數(shù)初始化Animal實(shí)例,這個(gè)Animal實(shí)例持有著指向方法區(qū)的Animal類(lèi)的類(lèi)型信息(其中包含有方法表,java動(dòng)態(tài)綁定的底層實(shí)現(xiàn))的引用。
5、當(dāng)使用animal.printName()的時(shí)候,JVM根據(jù)animal引用找到Animal對(duì)象,然后根據(jù)Animal對(duì)象持有的引用定位到方法區(qū)中Animal類(lèi)的類(lèi)型信息的方法表,獲得printName()函數(shù)的字節(jié)碼的地址。
6、開(kāi)始運(yùn)行printName()函數(shù)。
特別說(shuō)明:java類(lèi)中所有public和protected的實(shí)例方法都采用動(dòng)態(tài)綁定機(jī)制,所有私有方法、靜態(tài)方法、構(gòu)造器及初始化方法《clinit》都是采用靜態(tài)綁定機(jī)制。而使用動(dòng)態(tài)綁定機(jī)制的時(shí)候會(huì)用到方法表,靜態(tài)綁定時(shí)并不會(huì)用到。本文只是講述java程序運(yùn)行的大概過(guò)程,所以并沒(méi)有細(xì)加區(qū)分。
Java程序編譯運(yùn)行程序詳解
1、打開(kāi)java軟件,然后把點(diǎn)擊上方的菜單欄的“文件”,會(huì)出現(xiàn)一個(gè)菜單,點(diǎn)擊第一個(gè)“新建”,會(huì)出現(xiàn)一個(gè)二級(jí)菜單,然后點(diǎn)擊“java項(xiàng)目”。
2、之后,會(huì)彈出一個(gè)“新建Java項(xiàng)目”頁(yè)面。
首先,在“項(xiàng)目名”那里填寫(xiě)上你的項(xiàng)目的名字。
接著,設(shè)置項(xiàng)目的位置。(要設(shè)置項(xiàng)目的位置的話,你得先吧那個(gè)“使用缺省位置”復(fù)選框去掉,你才能設(shè)置。)
設(shè)置好之后,點(diǎn)擊“完成”。便會(huì)新建出一個(gè)項(xiàng)目。
3、如果沒(méi)有彈出頁(yè)面,那么你可以點(diǎn)擊在歡迎頁(yè)面的右上角的工作臺(tái)。
4、點(diǎn)擊之后會(huì)出現(xiàn)一個(gè)頁(yè)面,你可以在頁(yè)面的右上角點(diǎn)擊一個(gè)叫“java”的按鈕,接著會(huì)出現(xiàn)一個(gè)新的頁(yè)面,如圖所示。
5、找到“包資源管理器”,那里有一個(gè)你剛才創(chuàng)建項(xiàng)目的文件夾,右擊它會(huì)出現(xiàn)一個(gè)菜單,點(diǎn)擊“新建”,會(huì)出現(xiàn)一個(gè)二級(jí)菜單,點(diǎn)擊“類(lèi)”。
6、接著會(huì)彈出一個(gè)“新建Java類(lèi)”頁(yè)面。
在名稱(chēng)那里輸入類(lèi)的名稱(chēng)。
然后在“Public static void main (String[] args)”復(fù)選框打上勾。
點(diǎn)擊“完成”。
7、之后,在“包資源管理器”那里會(huì)出現(xiàn)一些文件。
頁(yè)面中部會(huì)彈出一個(gè)小頁(yè)面,那就是程序的編輯器。
8、在程序編輯器的
“public static void main(String[] args) {
}”
這段程序的中括號(hào)里面輸入System.out.println(“hello world!”);
這段程序。
9、然后在菜單欄里找到“運(yùn)行”并點(diǎn)擊,會(huì)跳出一個(gè)二級(jí)菜單,然后點(diǎn)擊運(yùn)行。
10、如果你沒(méi)保存的話,他會(huì)跳出一個(gè)如圖所示的界面,點(diǎn)擊“確定”即可。
11、然后頁(yè)面下方會(huì)出現(xiàn)程序的運(yùn)行結(jié)果。
評(píng)論