從Java文件到字節(jié)碼文件

本文涉及的javac編譯器來自openjdk.

javac的目錄地址為:

解壓目錄/langtools/src/share/classes/com/sun/tools/javac/

javac編譯器將Java編譯成為一個(gè)有效的字節(jié)碼文件會(huì)經(jīng)歷4個(gè)步驟:

  • 詞法解析:將Java關(guān)鍵字排序,使得程序能有序運(yùn)行叮盘。
  • 語法解析:詞法解析后的Token序列整合為一顆抽象的語法樹糯彬。
  • 語義解析:將抽象語法樹擴(kuò)展地更加完善溺森。
  • 字節(jié)碼解析:將字節(jié)碼解析成完整的類。

詞法解析


詞法解析是編譯器執(zhí)行的字節(jié)碼編譯的第一步邢隧。這個(gè)步驟中,將Java源碼中關(guān)鍵字和標(biāo)識符等轉(zhuǎn)換成符合規(guī)范的Token序列。

詞法解析器的接口是com.sun.tools.javac.parser.Lexer ,它直接派生于同包下面的Scanner類陨舱,它的主要任務(wù)是按照單個(gè)字符的方式讀取Java源文件中的關(guān)鍵字標(biāo)識符等,然后將其轉(zhuǎn)換為符合Java規(guī)范的Token序列版仔。而負(fù)責(zé)詞法解析工作的是com.sun.tools.javac.parser.JavacParser類游盲,該類的對象實(shí)例由ParseFactory負(fù)責(zé)創(chuàng)建,JavacParser負(fù)責(zé)詞法解析的具體細(xì)節(jié)蛮粮。

當(dāng)我們在命令行敲入javac的時(shí)候背桐,Java首先會(huì)調(diào)用com.sun.tools.javac.main.Main類的compile()方法。compile()方法接著就會(huì)調(diào)用JavaCompiler類的parseFile()方法蝉揍,parseFile()的主要功能就是調(diào)用自己的parse()方法獲得JavacParser實(shí)例對象链峭,然后調(diào)用JavacParser類的parseCompilationUnit()進(jìn)行詞法解析。

這個(gè)過程如下圖所示:

語法解析過程

Token序列

Token其實(shí)就是一個(gè)枚舉類型又沾,其內(nèi)部定了許多符合Java語法規(guī)范并與源碼字符集相對應(yīng)的枚舉常量弊仪。

所有的枚舉常量都在 com.sun.tools.javac.parser.Token類中。

編譯器在執(zhí)行詞法解析的過程中杖刷,只會(huì)對Token進(jìn)行匹配校驗(yàn)励饵。

源碼字符集是如何轉(zhuǎn)換成Token的:

Name對象和Token對象建立的是一種一對一的關(guān)系。當(dāng)詞法解析器中需要將一個(gè)源碼字符集合解析成一個(gè)Token時(shí)滑燃,它會(huì)通過Names類調(diào)用Name類的fromChars()方法獲得一個(gè)Name對象役听,然后使用Keyswords類的key(Name name)方法獲得傳入相對應(yīng)的Token對象。

詞法解析器如何保存源碼字符集和Token之間的對應(yīng)關(guān)系:

詞法解析器在將源碼轉(zhuǎn)字符集合轉(zhuǎn)換為Token之前,會(huì)先將每一個(gè)字符集合都轉(zhuǎn)換成一個(gè)對應(yīng)的Name對象典予。接著再由com.sun.tools.javac.parser.Keywords類負(fù)責(zé)實(shí)際的Token轉(zhuǎn)換任務(wù)(將Token常量全部轉(zhuǎn)換為Name對象)甜滨,然后轉(zhuǎn)換好的這些Name對象全部存到Name類的內(nèi)部類Table中,Keywords類中的數(shù)組key用于保存源碼字符集合和Token之間的對應(yīng)關(guān)系瘤袖。

以上兩個(gè)問題略微有些復(fù)雜衣摩。畫了一個(gè)圖來表示一下:

源碼字符集和Token之間的對應(yīng)關(guān)系

調(diào)用nextToken()計(jì)算Token的獲取順序

Keywords類的key()方法僅僅是根據(jù)Name對象獲得對應(yīng)的Token,而詞法解析器是通過Scanner類的nextToken()方法保證Token的讀取順序規(guī)則捂敌。

調(diào)用parseCompilationUnit()方法執(zhí)行詞法解析

詞法解析的核心是校驗(yàn)Token是否匹配com.sun.tools.javac.parser.JavacParser類在parseCompilationUnit()方法定義的匹配規(guī)則艾扮。parseCompilationUnit()方法會(huì)按照Token的匹配順序依次解析出package、import等關(guān)鍵字占婉,當(dāng)這些Token匹配之后泡嘴,詞法解析器會(huì)開始解析class主題信息,直到詞法解析全部結(jié)束逆济,parseCompilationUnit()方法會(huì)將Token轉(zhuǎn)換為一棵結(jié)構(gòu)化的抽象語法樹酌予。

語法解析


之前提過,語法解析的目的就是將經(jīng)過詞法解析得到的Token整合為一棵結(jié)構(gòu)化的抽象語法樹纹腌。

詞法解析完成的Token序列依舊還不完善霎终,它們還沒有被整合起來,語法解析的主要任務(wù)是把這些零散的Token按照指定的Java語法規(guī)范整合起來形成一個(gè)有機(jī)的整體升薯。

在語法解析階段莱褒,語法樹上每個(gè)節(jié)點(diǎn)都直接或者間接地繼承了JCTree類。

調(diào)用qualident()方法解析package語法節(jié)點(diǎn)

parseCompilationUnit()這個(gè)方法實(shí)際上涎劈,跨越了詞法解析和語法解析兩個(gè)階段广凸。當(dāng)詞法解析器成功將package關(guān)鍵字聲明轉(zhuǎn)換為Token并完成詞法解析之后,會(huì)調(diào)用qualident()方法根據(jù)Token.PACKAGE解析為package語法節(jié)點(diǎn)蛛枚。

語法解析步驟中谅海,com.sun.tools.javac.tree.TreeMaker負(fù)責(zé)創(chuàng)建JCTree類的所有語法節(jié)點(diǎn)對象實(shí)例。所以TreeMaker本身就是一個(gè)語法解析器蹦浦,不過具體的細(xì)節(jié)由parseCompilationUnit()方法來控制扭吁。

語法解析器實(shí)質(zhì)上還是使用Token對應(yīng)的Name對象,來作為轉(zhuǎn)換語法節(jié)點(diǎn)的素材盲镶。所以在解析語法樹之前侥袜,首先需要將Token轉(zhuǎn)換成對應(yīng)的Name對象。語法解析器就可以根據(jù)Name對象解析出一個(gè)JCIdent語法節(jié)點(diǎn)溉贿。

當(dāng)一個(gè)package關(guān)鍵字聲明中定義了多級目錄時(shí)枫吧,qualident()方法就會(huì)循環(huán)迭代調(diào)用語法解析器將package關(guān)鍵字聲明解析為嵌套的JCFieldAccess語法節(jié)點(diǎn)。

調(diào)用importDeclaration()方法解析import語法樹

當(dāng)成功解析出package語法節(jié)點(diǎn)之后宇色,parseCompilationUnit()這個(gè)節(jié)點(diǎn)會(huì)調(diào)用importDeclaration()方法來解析得到import語法樹九杂。

importDeclaration()首先匹配Token.Static來檢測import語句中是否包含了靜態(tài)導(dǎo)入颁湖。接著importDeclaration()就會(huì)調(diào)用語法解析器的Ident()方法解析出一個(gè)JCIdent語法節(jié)點(diǎn),如果import語句中包含多級目錄的時(shí)候例隆,語法解析器就會(huì)調(diào)用Select()方法解析為嵌套的JCFieldAccess語法節(jié)點(diǎn)甥捺。

當(dāng)語法解析器成功解析出JCIdent和JCFieldAccess節(jié)點(diǎn)之后,importDeclaration()方法會(huì)調(diào)用import()方法裳擎,將之前解析的語法節(jié)點(diǎn)涎永,整合成為一棵JCImport節(jié)點(diǎn)思币。

實(shí)際開發(fā)中鹿响,通常會(huì)有多個(gè)import關(guān)鍵字聲明,那么importDeclaration()方法內(nèi)部會(huì)通過迭代循環(huán)方法解析出多個(gè)JCImport語法樹谷饿,然后將其存儲在一個(gè)集合中惶我。

調(diào)用classDeclaration()方法解析class語法樹

語法解析的最后一步就是解析class的主體信息,當(dāng)語法解析器成功將import關(guān)鍵字解析聲明為JCIdent和JCFieldAccess語法節(jié)點(diǎn)并整合為一顆JCImport語法樹之后博投,parseCompilationUnit()方法內(nèi)部通過typeDeclaration()方法調(diào)用classOrInterfaceOrEnumDeclaration方法將class主體信息解析為一棵JCClassDecl語法樹绸贡。

當(dāng)以上這個(gè)過程結(jié)束之后,parseCompilationUnit()方法會(huì)調(diào)用TopLevel()方法將之前解析好過的package語法節(jié)點(diǎn)毅哗,import語法節(jié)點(diǎn)和class語法樹等內(nèi)容內(nèi)容信息全部整合成一棵JCCompilationUnit語法節(jié)點(diǎn)樹听怕。

JCCompilationUnit類會(huì)成為整個(gè)語法樹的Root,持有整個(gè)語法樹的所有節(jié)點(diǎn)虑绵。這個(gè)時(shí)候尿瞭,語法樹的雛形已經(jīng)建成。

語義解析


經(jīng)過了以上兩個(gè)步驟翅睛,解析完成的語法樹依舊不能進(jìn)入字節(jié)碼的編譯声搁,它還不夠完善。語義解析的任務(wù)就是將這個(gè)這顆不夠完善的語法樹擴(kuò)充地更加完善捕发。

語義解析步驟中經(jīng)歷的操作:

  • 為沒有構(gòu)造方法的類型添加默認(rèn)的無參構(gòu)造函數(shù)
  • 檢查任何變量是否在使用之前都被初始化
  • 檢查變量類型和值是不是匹配
  • 將String類型和常量進(jìn)行合并處理
  • 檢查代碼中的操作是不是都可達(dá)
  • 異常檢查
  • 將語法糖的內(nèi)容正呈柚迹化

常量折疊操作

如果一個(gè)String類型的數(shù)據(jù)是由多個(gè)常量通過『+』組成的,它其實(shí)只會(huì)創(chuàng)建一個(gè)String對象扎酷,編譯器在語義解析的時(shí)候檐涝,會(huì)將多個(gè)常量信息合并為一個(gè)對象。

//源代碼中的寫法
String str="Hello"+" "+"World!"
//經(jīng)過編譯器編譯之后
String str="Hello World!"

生成字節(jié)碼


在經(jīng)歷了一系列的語義解析之后法挨,所解析出來的語法樹就足夠完善了谁榜。這個(gè)時(shí)候編譯器最后的任務(wù)就是調(diào)用com.sun.tools.javac.jvm.Gen類,將這棵語法樹編譯為Java字節(jié)碼文件坷剧。

這個(gè)時(shí)候惰爬,符合Java規(guī)范的Java代碼就轉(zhuǎn)換成符合Java規(guī)范的字節(jié)碼文件了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惫企,一起剝皮案震驚了整個(gè)濱河市撕瞧,隨后出現(xiàn)的幾起案子陵叽,更是在濱河造成了極大的恐慌,老刑警劉巖丛版,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巩掺,死亡現(xiàn)場離奇詭異,居然都是意外死亡页畦,警方通過查閱死者的電腦和手機(jī)胖替,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豫缨,“玉大人独令,你說我怎么就攤上這事『冒牛” “怎么了燃箭?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舍败。 經(jīng)常有香客問我招狸,道長,這世上最難降的妖魔是什么邻薯? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任裙戏,我火速辦了婚禮,結(jié)果婚禮上厕诡,老公的妹妹穿的比我還像新娘累榜。我一直安慰自己,他們只是感情好木人,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布信柿。 她就那樣靜靜地躺著,像睡著了一般醒第。 火紅的嫁衣襯著肌膚如雪渔嚷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天稠曼,我揣著相機(jī)與錄音形病,去河邊找鬼。 笑死霞幅,一個(gè)胖子當(dāng)著我的面吹牛漠吻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播司恳,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼途乃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扔傅?” 一聲冷哼從身側(cè)響起耍共,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤烫饼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后试读,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杠纵,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年钩骇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了比藻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倘屹,死狀恐怖银亲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唐瀑,我是刑警寧澤瞳收,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布鸽照,位于F島的核電站,受9級特大地震影響正歼,放射性物質(zhì)發(fā)生泄漏赠尾。R本人自食惡果不足惜力穗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望气嫁。 院中可真熱鬧当窗,春花似錦、人聲如沸寸宵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梯影。三九已至巫员,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甲棍,已是汗流浹背简识。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留感猛,地道東北人七扰。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像陪白,于是被迫代替她去往敵國和親颈走。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 簡介網(wǎng)絡(luò)瀏覽器很可能是使用最廣的軟件咱士。在這篇入門文章中立由,我將會(huì)介紹它們的幕后工作原理袖瞻。我們會(huì)了解到,從您在地址欄輸...
    wengjq閱讀 1,999評論 2 15
  • 標(biāo)題: Rakudo and NQP Internals子標(biāo)題: The guts tormented imple...
    焉知非魚閱讀 1,342評論 1 3
  • 從小就喜歡做一些手工拆吆,其實(shí)也是因?yàn)楸绕鸪鲩T聋迎,還是更愿意在家里安靜的做點(diǎn)東西。 最開始我是在某寶上買材料包枣耀,但是很多...
    zeng小jia閱讀 342評論 0 1
  • 我2017年的目標(biāo)是創(chuàng)造豐盛霉晕,用副業(yè)使得年收入增加3萬元,其中一萬元用于一對一公益捐助貧苦學(xué)生捞奕;成熟的良好的溝通牺堰,...
    糖雪球球2閱讀 105評論 0 1
  • (一) “他要娶別人了,不是我……” 小左一遍一遍地颅围,自言自語著“不是我”伟葫。淡淡的神色,輕輕的語調(diào)仿佛在陳述一個(gè)無...
    王娟_閱讀 507評論 0 0