java执行class找不到main函数_你所不知道的HelloWorld背后的执行原理
專(zhuān)注于Java領(lǐng)域優(yōu)質(zhì)技術(shù),歡迎關(guān)注
作者:飯談編程
【今日最佳】對(duì)于程序員而言,所謂的二八定律指的是 花百分之八十的時(shí)間去學(xué)習(xí)日常研發(fā)中不常見(jiàn)的那百分之二十的原理。
據(jù)說(shuō)阿里某程序員對(duì)書(shū)法十分感興趣,退休后決定在這方面有所建樹(shù)。于是花重金購(gòu)買(mǎi)了上等的文房四寶。
一日,飯后突生雅興,一番磨墨擬紙,并點(diǎn)上了上好的檀香,頗有王羲之風(fēng)范,又具顏真卿氣勢(shì),定神片刻,潑墨揮毫,鄭重地寫(xiě)下一行字:hello world。
當(dāng)然了,這是個(gè)專(zhuān)屬程序員的段子哈哈哈。
那么問(wèn)題來(lái)了,寫(xiě)了這么久的Hello World,大家確定自己了解自己寫(xiě)的東西背后是什么原理嗎?(o???)
【給出2分鐘,該知識(shí)點(diǎn)涉及到了Java程序執(zhí)行流程,包括編譯、加載和執(zhí)行,你是否能夠理清呢?】
接下來(lái)進(jìn)入嚴(yán)肅時(shí)間 (@ ̄ー ̄@)
與眾不同的Hello World
整個(gè)代碼的執(zhí)行過(guò)程可以分為三個(gè)階段:
- 代碼編譯
- 類(lèi)加載
- 類(lèi)執(zhí)行
代碼編譯
代碼編譯的作用就是將我們編寫(xiě)的 Main.java文件轉(zhuǎn)化為Main.class文件,.class在這里又被稱(chēng)為字節(jié)碼文件,打開(kāi)就是一堆的火星文【反正就是看不懂】,在這里我們可以將編譯的過(guò)程看作生產(chǎn)JVM原料的過(guò)程,使用的工具就是jdk提供的工具javac。 大致流程如下:
- 詞法分析,即將源代碼的字符流轉(zhuǎn)變?yōu)門(mén)oken集的過(guò)程。白話文描述下,就是在我們實(shí)際編程中,單個(gè)字符是最小單位,而實(shí)際上在編程過(guò)程中,標(biāo)記才是最小單位,如關(guān)鍵字、變量名、字面量、運(yùn)算符等都可以成為T(mén)oken,貌似還是有點(diǎn)蒙蔽,舉個(gè)例子(>﹏<),比如整型int在我們編程中它就是三個(gè)字符組成的,就是i、n、t 三個(gè)字符,而在編譯過(guò)程中它就是一個(gè)Token,不可拆分。
這個(gè)過(guò)程對(duì)我們來(lái)說(shuō)其實(shí)是完全屏蔽的,但是實(shí)際上它是現(xiàn)代經(jīng)典編譯原理的 套路,詞法分析也是為了給后面編譯做準(zhǔn)備的】
- 語(yǔ)法分析,通過(guò)詞法分析拿到Token集后,下一步就是構(gòu)建抽象語(yǔ)法樹(shù)了,所謂的抽象語(yǔ)法樹(shù)其實(shí)就是一種用來(lái)描述程序代碼語(yǔ)法結(jié)構(gòu)的樹(shù)形表示方式,其中語(yǔ)法樹(shù)的每一個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語(yǔ)法結(jié)構(gòu),如包、類(lèi)型、修飾符、運(yùn)算符等。
在我們眼中,Main.java已經(jīng)可以清晰理解到底寫(xiě)的是什么東西了,但是對(duì)于JVM來(lái)說(shuō)還是一臉懵逼的,所以才需要構(gòu)建成語(yǔ)法樹(shù),在這一步后就不會(huì)再對(duì)源碼文件進(jìn)行操作了,后續(xù)的操作都建立在抽象語(yǔ)法樹(shù)上
- 填充符號(hào)表,符號(hào)表是由一組符號(hào)地址和符號(hào)信息構(gòu)成的表格,這個(gè)表格在編譯的不同階段都會(huì)被用到,如在目標(biāo)代碼生成階段,會(huì)對(duì)符號(hào)名進(jìn)行地址分配,而符號(hào)表就是地址分配的依據(jù)。
- 語(yǔ)義分析,語(yǔ)義分析階段也可以說(shuō)是語(yǔ)義檢測(cè)階段,上面說(shuō)到語(yǔ)法分析會(huì)構(gòu)建一棵語(yǔ)法樹(shù),那么這棵語(yǔ)法樹(shù)是否是正確合理的,就由語(yǔ)義分析來(lái)做了,語(yǔ)義分析會(huì)通過(guò)標(biāo)注檢查和數(shù)據(jù)及控制流分析檢查兩步入手,在生成字節(jié)碼的最后一步信息把關(guān)。
- 字節(jié)碼生成,這是javac編譯過(guò)程的最后一個(gè)階段了,字節(jié)碼生成階段并不只是簡(jiǎn)簡(jiǎn)單單的將前面各個(gè)步驟生成的信息轉(zhuǎn)化成字節(jié)碼然后放入磁盤(pán)中,還進(jìn)行了少量的代碼添加和轉(zhuǎn)換工作,如我們自己重載的構(gòu)造函數(shù)。
編譯期到這里就結(jié)束了,那么由誰(shuí)來(lái)將這些原料傳輸給JVM虛擬機(jī)呢?這個(gè)時(shí)候就要看看類(lèi)加載的過(guò)程了。
類(lèi)加載
類(lèi)加載簡(jiǎn)單來(lái)說(shuō)就是將由類(lèi)加載器將編譯后的字節(jié)碼文件【Main.class】加載到虛擬機(jī)中 ,那么自然而然的,要先介紹下四種類(lèi)加載器
說(shuō)說(shuō)四種類(lèi)加載器
可以從上圖中看出,類(lèi)加載器可以分為四種,而第四種是由我們自己實(shí)現(xiàn)的,那么其他三種由JVM提供的類(lèi)加載在我們啟動(dòng)該Main程序的過(guò)程中起到了什么作用呢?
首先說(shuō)說(shuō)啟動(dòng)類(lèi)加載器 Bootstrap ClassLoader ,啟動(dòng)類(lèi)加載器的作用主要是加載 %JAVA_HOME%jrelib.jar 類(lèi)庫(kù),將其加載到虛擬機(jī)內(nèi)存中,那么rt.jar類(lèi)庫(kù)到底有什么作用呢?rt.jar下包含了Java的基礎(chǔ)類(lèi)庫(kù),也就是Java doc里面看到的所有的類(lèi)的class文件,感興趣的朋友可以自己打開(kāi)目錄看下。
其次是擴(kuò)展類(lèi)加載器 Extension ClassLoader ,擴(kuò)展類(lèi)加載器的作用主要是負(fù)責(zé)加載JAVA_HOMEjrelibext目錄下的所有類(lèi)庫(kù),主要是載入擴(kuò)展包。
再者是系統(tǒng)類(lèi)加載器 Application ClassLoader, 也稱(chēng)之為應(yīng)用程序類(lèi)加載器,負(fù)責(zé)加載用戶(hù)類(lèi)路徑(也就是我們配置的CLASSPATH)上所指定的類(lèi)庫(kù),是應(yīng)用程序中默認(rèn)的類(lèi)加載。
看完以上三個(gè)類(lèi)加載器的簡(jiǎn)單描述過(guò)程,是不是有種終于知道了我們配置的jdk環(huán)境的最終作用了吧,是的,就是讓類(lèi)加載器識(shí)別到后加載各種類(lèi)庫(kù)。
那么問(wèn)題來(lái)了?是哪個(gè)類(lèi)加載器加載了我們的Hello World程序呢?是的,就是應(yīng)用程序中默認(rèn)的類(lèi)加載器 Application ClassLoader。
知道了類(lèi)加載器后,那接下來(lái)總要了解下類(lèi)加載器怎么加載的吧?
說(shuō)說(shuō)類(lèi)加載的過(guò)程
網(wǎng)上找了張圖片,簡(jiǎn)單明了。雖說(shuō)是簡(jiǎn)單明了,不過(guò)確實(shí)異常重要的,因?yàn)槭敲嬖嚐狳c(diǎn)(????)
加載
其實(shí)就是上文說(shuō)到的系統(tǒng)類(lèi)加載器 Application ClassLoader將編譯后的Main.class文件加載到內(nèi)存中。
【思考】拋出個(gè)問(wèn)題,所謂的加載到內(nèi)存中,我們都知道JVM把內(nèi)存分成了幾大模塊,那么請(qǐng)問(wèn)是加載到哪個(gè)模塊中?熱點(diǎn)面試題,答案見(jiàn)文末!
鏈接
鏈接中包含了三部曲,總的作用就是負(fù)責(zé)將Main.class的二進(jìn)制數(shù)據(jù)合并到JRE中。
關(guān)于三部曲,其實(shí)很好理解;
首先是驗(yàn)證階段,類(lèi)加載器將二進(jìn)制字節(jié)流加載到虛擬機(jī)中,肯定是需要進(jìn)行驗(yàn)證的,避免危害虛擬機(jī)自身安全,而這也是驗(yàn)證階段存在的價(jià)值;
接下來(lái)是準(zhǔn)備階段,準(zhǔn)備階段是正式為類(lèi)變量分配內(nèi)存并且設(shè)置類(lèi)變量默認(rèn)值的地方,比如上面HelloWorld程序中的
private static String word = "Hello World!";注意我描述的第一個(gè)是類(lèi)變量,也就是static所描述的變量,其次是默認(rèn)值,也就是上面的word的默認(rèn)值null,如果是數(shù)字則為0。
最后是解析階段,解析階段的作用主要是將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,解析階段其實(shí)有點(diǎn)難理解,至少是比上面的兩個(gè)階段要難理解的,我這里盡量直白點(diǎn);
所謂的符號(hào)引用指的是包含了類(lèi)的信息、方法名、方法參數(shù)等信息的字符串,而當(dāng)?shù)谝淮芜\(yùn)行時(shí),JVM會(huì)根據(jù)這行字符串去檢索到對(duì)應(yīng)的方法入口,而為了下次不用再做同樣的檢索,在第一次運(yùn)行的時(shí)候就會(huì)將符號(hào)引用替換成直接引用,這樣后面就可以省去一定的消耗了;這里的直接引用其實(shí)就是指偏移量,虛擬機(jī)可以通過(guò)偏移量直接找到方法入口,不再需要做檢索了。
初始化 終于來(lái)到初始化階段了,上面我們有說(shuō)到word默認(rèn)值是null,是系統(tǒng)賦的默認(rèn)值,而在初始化階段,則是根據(jù)我們?nèi)藶榈某跏蓟?lèi)變量和其他資源,比如上面的word則被我初始化成了"Hello World!"。
類(lèi)執(zhí)行
上面說(shuō)到Main.class被加載到了Java虛擬機(jī)內(nèi)存中,那么接下來(lái)便是執(zhí)行的過(guò)程了。那么由誰(shuí)來(lái)執(zhí)行這一過(guò)程呢? 如圖
- 實(shí)際上,一個(gè)Java虛擬機(jī)在運(yùn)行的時(shí)候可以劃分為三個(gè)子系統(tǒng):類(lèi)加載子系統(tǒng)
- 執(zhí)行引擎子系統(tǒng)
- 垃圾收集子系統(tǒng)
很明顯、很清晰,圖中的類(lèi)加載子系統(tǒng)在上面已經(jīng)談了,執(zhí)行引擎子系統(tǒng)就是負(fù)責(zé)執(zhí)行這一部分的,那么過(guò)程是怎么樣的呢?
其實(shí)很簡(jiǎn)單,執(zhí)行引擎會(huì)把字節(jié)碼轉(zhuǎn)換為機(jī)器碼【what?竟然還要轉(zhuǎn)換。拜托,字節(jié)碼是被JVM識(shí)別的語(yǔ)言,機(jī)器碼才是最終被操作系統(tǒng)識(shí)別的語(yǔ)言】
然后操作系統(tǒng)才可以真正調(diào)用,很多學(xué)或者做Java的人都聽(tīng)過(guò)JIT,但是都不知道具體是干嘛的,沒(méi)錯(cuò)說(shuō)的就是你。
這里終于可以解釋下了,字節(jié)碼轉(zhuǎn)換成機(jī)器碼的翻譯工作使用的就是JIT(Just In Time)即時(shí)編譯器(對(duì)熱代碼整段編譯)和Java字節(jié)碼解釋器(一行一行解釋字節(jié)碼)來(lái)完成的。 這里給下JIT編譯的工作流程:
JVM字節(jié)碼 -> 機(jī)器無(wú)關(guān)優(yōu)化 -> 中間代碼 -> 機(jī)器相關(guān)優(yōu)化 -> 中間代碼 -> 寄存器分配器 -> 中間代碼 -> 目標(biāo)機(jī)器碼生成器 -> 目標(biāo)機(jī)器碼最后執(zhí)行引擎會(huì)找到main()這個(gè)入口方法,并且執(zhí)行其中的字節(jié)碼指令。
最后,關(guān)于HelloWorld執(zhí)行過(guò)程,基本上闡述完畢了,關(guān)于執(zhí)行程序期間,JVM內(nèi)存分配問(wèn)題,是一個(gè)比較大的模塊,我們下次再聊!!!
【思考解惑】加載階段完成后,虛擬機(jī)會(huì)將Main.class的二進(jìn)制字節(jié)流按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,然后在內(nèi)存中實(shí)例化一個(gè)java.lang.Class類(lèi)的對(duì)象,作為程序訪問(wèn)方法區(qū)中的這些類(lèi)型數(shù)據(jù)的外部接口,實(shí)例化后的java.lang.Class類(lèi)的對(duì)象也是存放在方法區(qū)中的。
總結(jié)
以上是生活随笔為你收集整理的java执行class找不到main函数_你所不知道的HelloWorld背后的执行原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: FMI在仿真软件SkyEye中的应用
- 下一篇: ModelCoder国产化解决方案已逐步