來(lái)源:北大青鳥(niǎo)總部 2023年07月12日 09:56
在程序員的世界中,你總會(huì)聽(tīng)到一句“PHP是世界上最好的語(yǔ)言”的調(diào)侃。然而在你進(jìn)入軟件程序開(kāi)發(fā)之后,你會(huì)發(fā)現(xiàn)即使開(kāi)發(fā)語(yǔ)言千千萬(wàn),最盛行的還是JAVA。從淘寶的技術(shù)變遷中我們可以見(jiàn)一些端倪,早期電商剛起來(lái)的時(shí)候,那會(huì)兒的互聯(lián)網(wǎng)還很簡(jiǎn)單,使用PHP+Mysql+Apache+Linux就可以快速搭建起一套電商系統(tǒng),但隨著電商平臺(tái)、支付平臺(tái)的完善,網(wǎng)上購(gòu)物開(kāi)始變得簡(jiǎn)單,越來(lái)越多的人使用淘寶購(gòu)物了,淘寶的技術(shù)架構(gòu)也開(kāi)始不斷的升級(jí),增加服務(wù)器數(shù)量來(lái)提高系統(tǒng)可用性。
通過(guò)運(yùn)維手段擴(kuò)充資源是一種方式,治標(biāo)不治本,最根本的原因還是在于PHP這種語(yǔ)言可擴(kuò)展性不夠,用戶量十萬(wàn)、百萬(wàn)、千萬(wàn)的時(shí)候都還能支撐,但到了上億、億萬(wàn)的時(shí)候怎么擴(kuò)展都不行了。于是淘寶系統(tǒng)開(kāi)始一點(diǎn)點(diǎn)的前后端分離,后端使用JAVA語(yǔ)言開(kāi)發(fā),逐漸遷移業(yè)務(wù)。現(xiàn)在我們所使用的淘寶系統(tǒng),80%以上的后端程序都是Java開(kāi)發(fā),可見(jiàn)笑到最后才是贏家啊。不過(guò)JAVA語(yǔ)言的上手難度就比PHP、前端高很多了,所以今天我們給大家講解下一行JAVA代碼到底是如何運(yùn)行起來(lái)的,JAVA后浪們可以以此為入門(mén)Java的基礎(chǔ),開(kāi)啟Java開(kāi)發(fā)、人生贏家之路。
Java是一種半解釋型語(yǔ)言,相對(duì)的有解釋型語(yǔ)言Python&PHP、編譯型語(yǔ)言C&C++。解釋型語(yǔ)言說(shuō)的是只需要在客戶端屬于代碼后就可以運(yùn)行起來(lái),實(shí)時(shí)看到結(jié)果,編譯型語(yǔ)言說(shuō)的是源代碼需要進(jìn)行構(gòu)建編譯成二進(jìn)制文件才能在機(jī)器運(yùn)行起來(lái),半解釋型語(yǔ)言介于其中,它把輸入的代碼進(jìn)行編譯,編譯后在JVM虛擬機(jī)中運(yùn)行(注:JVM虛擬機(jī)是在實(shí)際的機(jī)器中運(yùn)行的)。半解釋型語(yǔ)言的好處就是可以跨平臺(tái),一次編譯,多次執(zhí)行。
我們通過(guò)下面這邊Java程序,來(lái)講明Java程序從編譯到最后運(yùn)行到整個(gè)流程。JVM運(yùn)行Java程序有兩種方式,分別是jar包和Class類(lèi)文件,jar包是偏上層的方式,把所有程序都打包成一個(gè)jar包,便于交付測(cè)試人員測(cè)試、運(yùn)維人員發(fā)布,它的運(yùn)行邏輯是通過(guò)java.exe找到j(luò)ava自帶的GetMainClassName函數(shù),該函數(shù)獲取JNIENV實(shí)例,并調(diào)用JarFileJNIENV實(shí)例中的GetMainfest()函數(shù)獲取MainClass函數(shù),Main函數(shù)再調(diào)用Java.c中的LoadClass方法加載主類(lèi)。
而Class方式則是越過(guò)上層,直接通過(guò)main函數(shù)調(diào)用Java.c中的LoadClass方法裝載類(lèi)。所以說(shuō)jar運(yùn)行的方式本質(zhì)上也是class類(lèi)運(yùn)行的方式,因此我們來(lái)關(guān)注如何類(lèi)方式如何加載運(yùn)行就好了。下面代碼想實(shí)現(xiàn)的功能是打印Code這個(gè)字符,整體代碼如下。我們先定義了一個(gè)類(lèi)HelloJava,在這個(gè)類(lèi)新建了一個(gè)對(duì)象去打印Code字符,而這個(gè)對(duì)象又調(diào)用了類(lèi)Product.java
在整個(gè)代碼的運(yùn)行中,包含兩步,第一步是編譯,第二步是運(yùn)行。源文件創(chuàng)建完之后,使用javac就可以編譯.java程序,程序會(huì)被編譯成.class文件,使用java命令就可以運(yùn)行.class文件。編譯后的文件有代碼中出現(xiàn)過(guò)的類(lèi)名&變量名&方法引用名、類(lèi)中各個(gè)方法的字節(jié)碼,它們分別存儲(chǔ)在常量池、方法字節(jié)碼中。
在Java程序的編譯過(guò)程中,如果該類(lèi)所依賴的類(lèi)還沒(méi)有被編譯,編譯器就會(huì)先編譯被依賴的類(lèi),如果依賴類(lèi)編譯了則直接引用。在Java類(lèi)的運(yùn)行中,包含加載和運(yùn)行兩個(gè)步驟。.class文件就是通過(guò)類(lèi)加載器到j(luò)vm當(dāng)中的。在Java中默認(rèn)有三種類(lèi)加載器,從下往上依次是自定義類(lèi)加載器UserClassLoader(負(fù)責(zé)加載自定義的class文件)、應(yīng)用類(lèi)加載器AppClassLoader(負(fù)責(zé)加載classpath指定的jar包和目錄中的class文件)、擴(kuò)展類(lèi)加載器ExClassLoader(負(fù)責(zé)加載Java平臺(tái)中擴(kuò)展功能的jar包)、啟動(dòng)類(lèi)加載器BootstrapClassLoader(負(fù)責(zé)加載$JAVA_Home中jre/lib/rt.jar中所有的class類(lèi))。當(dāng)AppClassLoader接收到一個(gè)類(lèi)加載命令后,它不會(huì)自己先去加載,而是給到擴(kuò)展類(lèi)加載器,同樣擴(kuò)展類(lèi)加載器自己也不會(huì)先去加載類(lèi),而是把它給到啟動(dòng)類(lèi)加載器去加載,如果失敗再層層往下傳遞。所以Java是動(dòng)態(tài)在加載類(lèi)。
回到我們剛剛的例子中,在編譯好Java程序后,我們得到HelloJava.class文件,在終端我們輸入javaHelloJava,系統(tǒng)就會(huì)啟動(dòng)一個(gè)JVM進(jìn)程,JVM進(jìn)程從classpath的路徑中尋找命名為HelloJava.class的二進(jìn)制文件,將HelloJava的類(lèi)加載信息加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū),找到HelloJava的主函數(shù)入口,執(zhí)行Main函數(shù)。Main函數(shù)的第一條命令是Productproduct = newProduct(“Code”),它需要JVM創(chuàng)建一個(gè)Product對(duì)象,但此時(shí)方法區(qū)中沒(méi)有沒(méi)有Product類(lèi)的信息,于是JVM加載Product類(lèi),把Product類(lèi)的類(lèi)型信息放在方法區(qū)中。加載完了Product類(lèi)之后,JVM虛擬機(jī)在堆區(qū)為新的Product實(shí)例分配內(nèi)存,初始化類(lèi)。在調(diào)用product.printName()方法的時(shí)候,JVM根據(jù)Product引用找到Product對(duì)象,根據(jù)Product對(duì)象持有的引動(dòng)定位到方法區(qū)中的Animal類(lèi)的類(lèi)型信息方法表,獲取printName()函數(shù)的字節(jié)碼地址,運(yùn)行printName()函數(shù),打印出來(lái)“Code”。
微觀的編譯執(zhí)行介紹完了,我們來(lái)看看中觀的執(zhí)行。在介紹Java是解釋型語(yǔ)言時(shí),我們有講到JVM是跨平臺(tái)執(zhí)行的,也就是一份Java代碼編譯之后,可以在Linux、unix、Windows、Macos等操作系統(tǒng)平臺(tái)中執(zhí)行。我們一起來(lái)看看是如何實(shí)現(xiàn)的呢?在Java程序運(yùn)行中有三個(gè)概念,JVM、JDK、JRE。
所謂JVM就是Javavirtual Machine,Java虛擬機(jī),執(zhí)行Java代碼;
所謂JDK是指的JavaDevelopment kit,Java開(kāi)發(fā)工具包,Java開(kāi)發(fā)人員使用;
所謂JRE就是JavaRuntimeEnvironment,Java運(yùn)行時(shí)環(huán)境。
JVM屬于JRE,JRE屬于JDK。在JDK的安裝中,有不同的版本,比如Linuxx86、Windowsx64,只要安裝了JDK之后,就由JDK來(lái)區(qū)分操作系統(tǒng),JVM是運(yùn)行在操作系統(tǒng)之上,區(qū)分操作系統(tǒng)的任務(wù)就是由JDK來(lái)完成的,只要你的電腦裝了JDK,任何一份Class字節(jié)碼都會(huì)運(yùn)行在JVM中,JVM又可以運(yùn)行在任意操作系統(tǒng)中,從而實(shí)現(xiàn)了“跨平臺(tái)一次編譯,多次執(zhí)行”。
講完了中觀的執(zhí)行,我們來(lái)看看宏觀執(zhí)行。我們程序員在寫(xiě)Java代碼時(shí),都會(huì)把程序編譯成jar包,通過(guò)jar包來(lái)運(yùn)行程序。一個(gè)jar包代表了一個(gè)功能模塊的實(shí)現(xiàn),如果某個(gè)jar包有我們想要使用的功能,就在程序中引用就好。然而業(yè)務(wù)功能在開(kāi)發(fā)實(shí)現(xiàn)時(shí)可運(yùn)行依賴的jar包很多,如果把每個(gè)功能所實(shí)現(xiàn)的jar包都放在自己的jar包中,就會(huì)非常的浪費(fèi)資源和運(yùn)行效率。這時(shí)候我們可以把程序依賴的jar包都放在一個(gè)單獨(dú)的文件夾中,然后修改jar包中“META-INF”目錄下的“MANIFEST.MF”清單文件即可。在manifest文件中,我們指定Manifest文件的版本,運(yùn)行主類(lèi)的名稱,程序所依賴的jar包的Classpath路徑都寫(xiě)明清楚,Java程序執(zhí)行時(shí)加載manifest文件即可。
本文詳細(xì)的介紹了一行JAVA代碼是如何在JVM系統(tǒng)中運(yùn)行起來(lái)的,對(duì)于有志加入互聯(lián)網(wǎng)行業(yè),使用Java語(yǔ)言開(kāi)發(fā)貢獻(xiàn)力量的朋友們來(lái)說(shuō),可以在初學(xué)時(shí)深刻的理解體會(huì)到Java代碼時(shí)怎么運(yùn)行起來(lái)的、JDK&JRE&JVM是什么?在面試的時(shí)候也能比較輕松從容的回到面試官問(wèn)題,在帶新人的時(shí)候也可以裝一把大佬。