? ? 軟件開發(fā)的環(huán)境需要什么?一個(gè)IDE充包,一個(gè)OS副签,一個(gè)硬件設(shè)備,沒錯(cuò)基矮,這個(gè)實(shí)質(zhì)是軟件進(jìn)展的三個(gè)層集淆储。在很久很久以前(幾十年),軟件就是直接開發(fā)在硬件設(shè)備上的家浇,用紙帶有無孔標(biāo)識二進(jìn)制位本砰,此時(shí)的開發(fā)語言是機(jī)器碼,軟件直接對接硬件設(shè)備钢悲;后來很不方便点额,尤其不方便復(fù)用,然后莺琳,有了匯編还棱,有了簡單的編譯環(huán)境,然后逐漸發(fā)展成為OS內(nèi)核惭等;時(shí)代會進(jìn)步珍手,軟件要處理越來越多復(fù)雜的場景,然后有了高級語言:C等,為了更加高效友好的開發(fā)琳要,有了最初期的IDE料扰,2000年左右的程序員,應(yīng)該用記得有一個(gè)Turbo C焙蹭,這個(gè)也是我的入門IDE,這個(gè)至少要比記事本寫代碼方便了一些嫂伞。軟件的規(guī)模越來越大孔厉,行業(yè)分工越來越細(xì),開發(fā)的效率也越發(fā)的重要帖努,以Android為例撰豺,從Eclipse到google官方所推的Android Studio,IDE的功能越來越強(qiáng)大拼余,這個(gè)解放了程序員污桦,但也控制了程序員,有誰還會知道匙监,寫在這些IDE中的代碼行凡橱,為什么可以執(zhí)行在手機(jī)上?中間有什么過程亭姥,是什么模塊在控制這些過程稼钩?是完全由IDE實(shí)現(xiàn)的嗎?我相信能答出這個(gè)問題的人是少數(shù)达罗。古人說過坝撑,要知其然,還要知其所以然粮揉,只會使用IDE去寫固定的功能模塊巡李,只是一個(gè)普通碼農(nóng),要想用好一個(gè)工具(包括IDE及語言)扶认,還要了解其實(shí)質(zhì)侨拦,要明白,我們寫好的代碼蝠引,是怎么運(yùn)行到硬件環(huán)境上的阳谍。這個(gè)話題很大,包括編譯原理螃概、OS結(jié)構(gòu)矫夯、硬件驅(qū)動(dòng)、各種語言等吊洼。大不是不去了解的理由训貌,技術(shù)的提交需要反復(fù)的打磨,好,先來揭個(gè)蓋子递沪,看一下C/C++及Java由代碼到機(jī)器碼的過程豺鼻,有了機(jī)器碼之后,再后面的運(yùn)行細(xì)節(jié)款慨,離軟件開發(fā)離得較遠(yuǎn)儒飒,和開發(fā)優(yōu)質(zhì)軟件平臺(除OS外)關(guān)系也不太大。
? ? 由源碼到機(jī)器碼檩奠,C/C++與Java的實(shí)現(xiàn)并不相同桩了,為什么要放在一起呢,三個(gè)原因吧:(一)從語言代來講埠戳,C為二代面向過程語言井誉、C++為三代面向?qū)ο笳Z言,Java為參考C++所設(shè)計(jì)的三代面向?qū)ο笳Z言整胃,其本身是有傳承的颗圣,語言會傳承,編譯運(yùn)行環(huán)境同樣是傳承的屁使,對比著看在岂,可以看出優(yōu)化方向;(二)我目前主要是在做Android開發(fā)的相關(guān)方面屋灌,這個(gè)是與自身最切身相關(guān)的語言洁段,Android的內(nèi)核是C/C++環(huán)境、應(yīng)用層和framework層是Java環(huán)境共郭,對這兩門語言有迫切的項(xiàng)目需求祠丝。(三)個(gè)人認(rèn)為,C/C++除嘹、Java作為靜態(tài)語言写半,其應(yīng)用范圍、語言特性尉咕,編譯運(yùn)行原理非常有代表性叠蝇,尤其是Java在做跨平臺,之后JVM上可以運(yùn)行其他的動(dòng)態(tài)語言年缎,可以說Java的編譯運(yùn)行可以代表一部分希望跨平臺的動(dòng)態(tài)語言悔捶,比如Kotlin。
? ? 先來看一下C/C++的源碼到機(jī)器語言過程發(fā)生了什么单芜,分為四個(gè)大步驟:預(yù)編譯蜕该、編譯、匯編洲鸠、鏈接堂淡,在C/C++中統(tǒng)稱為編譯馋缅。
? ? (一)預(yù)編譯所處理的過程包括:
? ? ? ? a.展開宏定義#define
? ? ? ? b.處理?xiàng)l件預(yù)編譯指令#if等
? ? ? ? c.處理#include
? ? ? ? d.刪除注釋
? ? ? ? e.為Debug及日志填加行號
? ? ? ? f.保留#pragma
? ? (二)編譯所處理的過程包括:
? ? ? ? a.詞法分析:使用掃描器將源碼分隔為一系列的記號(Token),即源碼中的不可分隔項(xiàng)绢淀,較為容易理解萤悴,比如下面的:
? ? ? ? int index = (2?+ 8) * c? 會被折分為 :int index = ( 2 + 8 ) * c? 10個(gè)token,左右半括號各為一個(gè)
? ? ? ? b.語法分析
? ? ? ? 將a中分出來的Token皆的,映射為語法樹
? ? ? ? c.語義分析
? ? ? ? 在b中語法樹的基礎(chǔ)上覆履,分析是否有錯(cuò)誤語義,編譯器所能分析的語義為靜態(tài)語義费薄,包括:聲明是否正確内狗、類型是否匹配、類型的轉(zhuǎn)換是否符合要求
? ? ? ? d.經(jīng)過前面三個(gè)過程后义锥,將源代碼轉(zhuǎn)換為中間語言,可以理解為將c中通過的語法樹通過一定的規(guī)則拍平岩灭,變?yōu)轭惣儆谀繕?biāo)代碼的結(jié)構(gòu)拌倍,為什么要引入中間語言呢,目標(biāo)語言很多時(shí)候是硬件相關(guān)的噪径,而中間語言與硬件無關(guān)柱恤。
? ? ? ? e.生成目標(biāo)代碼并進(jìn)行相應(yīng)優(yōu)化
? ? ? ? 目標(biāo)代碼的文件組織格式為.o文件,其內(nèi)部的格式與具體設(shè)備有很大關(guān)系找爱,比如在Android中梗顺,對arm7和x86CPU要編譯出不同的so文件,此處同理车摄,不同的硬件環(huán)境生成不同的目標(biāo)代碼寺谤。目標(biāo)代碼的優(yōu)化也是針對不同情況有不同處理,在此不再展開吮播,感興趣的同學(xué)可以參考編譯原理相關(guān)書籍变屁。
? ? (三)匯編:匯編的目的是把匯編語言轉(zhuǎn)為機(jī)器語言,基本是一條轉(zhuǎn)一條意狠,沒啥特殊的
? ? (四)鏈接:鏈接是要解決目標(biāo)文件之間的互相依賴關(guān)系粟关,當(dāng)a文件中的aa方法中調(diào)用了b文件的bb方法時(shí),在匯編完成后环戈,a文件的bb方法并沒有準(zhǔn)確的內(nèi)存地址闷板,鏈接后會轉(zhuǎn)換為虛擬地址,虛擬地址可以依據(jù)一定的規(guī)則轉(zhuǎn)換為實(shí)際地址院塞,即可以運(yùn)行時(shí)找到該方法遮晚。鏈接過程包括:地址和空間分配、符號決議和重定位迫悠,之后的文檔中可以再總結(jié)下鹏漆。
? ? Java語言從源代碼到機(jī)器碼的過程要比C/C++復(fù)雜,Java追求的是一次編譯,多次運(yùn)行的跨平臺特性艺玲,因而在分層上更為徹底括蝠。可以分為編譯期和運(yùn)行期兩個(gè)周期饭聚,編譯期的輸入為源代碼.java文件忌警、輸出為字節(jié)碼.class文件、運(yùn)行期輸入為字節(jié)碼.class文件秒梳,輸出為機(jī)器碼法绵。為何這么做可以跨平臺呢,JVM定義了嚴(yán)格的.class文件的格式酪碘,Java文件需要嚴(yán)格按照定義編譯為.class文件朋譬,然后可以拿到各個(gè)平臺各個(gè)版本的虛擬機(jī)上運(yùn)行。如果其他的語言編譯完畢后也遵循.class文件格式兴垦,也可以在JVM上和Java一樣運(yùn)行徙赢。
? ? 先來看Java的編譯期,有以下幾個(gè)過程:
? ? (一)生成語法樹及符號表的過程:
? ? ? ? a.首先進(jìn)行詞法分析探越,將源碼的字符流分解為token的集合狡赐,token的含義可以參考上面所提到的,即不可折分的個(gè)體
? ? ? ? b.語法分析钦幔,根據(jù)token集合構(gòu)建抽象語法樹
? ? ? ? c.生成符號表:符號表是一個(gè)類key-value結(jié)構(gòu)的集合枕屉,記錄符號的類型、結(jié)構(gòu)鲤氢、定義搀擂,支持增加與刪除
? ? (二)處理Java語言中的注解,修正(一)中生成的語法樹
? ? (三)語義分析卷玉,與C/C++的語義分析類似哥倔,進(jìn)行一些語義正確性檢測,具體包括:
? ? ? ? a.標(biāo)注檢查:類型聲明及賦值是否合適
? ? ? ? b.常量折疊:將 1 + 2 直接用3替代
? ? ? ? c.上下文的語法檢測揍庄,如變量使用前是否賦值等
? ? ? ? d.解語法糖咆蒿,為了提升開發(fā)效率,Java提供了許多的語法糖蚂子,比如:泛型沃测、變長參數(shù)、自動(dòng)裝箱等食茎,在此過程中蒂破,會將這些語法糖轉(zhuǎn)為JVM所規(guī)定的格式。具體可以查看Java虛擬機(jī)中相關(guān)語法編譯期的處理
? ? (四)生成字節(jié)碼别渔,生成字節(jié)碼階段主要是兩個(gè)事情:
? ? ? ? a.按照抽象語法樹及符號表生成相應(yīng)的字節(jié)碼
? ? ? ? b.針對Java語言的特點(diǎn)附迷,進(jìn)行些必要的填充惧互,比如:字符串的+轉(zhuǎn)為StringBuilder等
? ? 經(jīng)過上述四個(gè)過程,Java中的編譯期即進(jìn)行完畢了喇伯,可以獲取到.class文件喊儡,如果對.class文件格式感興趣,可以看我之前的文章:https://blog.csdn.net/kcstrong/article/details/79460262
? ? 繼續(xù)分析下Java的運(yùn)行期稻据,先看下運(yùn)行期有什么特點(diǎn)艾猜。首先,運(yùn)行期離Java語言的使用者捻悯,離得較遠(yuǎn)匆赃,運(yùn)行期處理的是字節(jié)碼到機(jī)器碼之間的轉(zhuǎn)換,Java語言的使用者不需要了解這些細(xì)節(jié)今缚,也可以進(jìn)行高效的開發(fā)算柳。理解這些細(xì)節(jié),以我當(dāng)前的體驗(yàn)姓言,對于開發(fā)者來講埠居,主要是增長知識面,對開發(fā)的影響也不大事期。那運(yùn)行期和什么樣的開發(fā)者關(guān)系較大呢,沒錯(cuò)纸颜,就是虛擬機(jī)開發(fā)人員或者需要優(yōu)化虛擬機(jī)的人員兽泣,比如,服務(wù)器的運(yùn)維人員胁孙。
? ? Java運(yùn)行期的工作并非是固定的唠倦,最基本的,也是最簡單的涮较,當(dāng)然稠鼻,也可以理解為保底的,是使用解釋器將字節(jié)碼轉(zhuǎn)換為機(jī)器碼狂票,該原理相當(dāng)?shù)那逦鞔_候齿,有以下步驟:
? ? a.按一定的規(guī)則尋找相關(guān)的類,首先闺属,根據(jù)環(huán)境變量找到j(luò)ava類庫的位置慌盯,然后根據(jù)雙親委托模式找到準(zhǔn)確的類位置
? ? b.然后就很簡單了,JVM字節(jié)碼可以直接逐條解釋掂器,只是JVM是基本于棧的指令集亚皂,條數(shù)較多,解釋較為耗時(shí)国瓮。
? ? 另一種Java運(yùn)行期的處理為JIT灭必,是一種Java動(dòng)態(tài)編譯方法狞谱,分為兩種:Client Compiler和Server Compiler
? ??Client Compiler較為簡單快速,其過程用下面一幅圖來說明:
? ?Server Compiler是一個(gè)經(jīng)過充分優(yōu)化的高級編譯器禁漓,包括但不限于:無用代碼擦除跟衅、基本塊重排序等,其技術(shù)極為復(fù)雜璃饱,感興趣的同學(xué)可以查閱專門的資料研究与斤。選用上述那種Compiler進(jìn)行處理,要視環(huán)境的需要而定荚恶。
? ? JIT并非完全取代解釋器撩穿,只是在需要優(yōu)化的高頻方法或高頻模塊(比如多次for循環(huán))才會介入,其他時(shí)候仍采用的是解釋器的處理方式谒撼。
? ? ?針對Java的源碼到機(jī)器碼的處理食寡,上面提到了兩種編譯運(yùn)行方式分別為:編譯為字節(jié)碼+解釋器、編譯為字節(jié)碼+JIT+解析器廓潜。目前還是另一種方式抵皱,為AOT,一種靜態(tài)編譯方式辩蛋,在編譯期呻畸,即將源碼編譯為機(jī)器碼,其好處也是顯然的悼院,運(yùn)行器無JVM處理時(shí)延伤为,效率最高,但缺點(diǎn)也很明顯:(1)犧牲了Java的跨平臺特性据途,機(jī)器碼一定是平臺相關(guān)的(2)編譯算法復(fù)雜度相當(dāng)高绞愚,JIT在運(yùn)行期,通過運(yùn)行時(shí)數(shù)據(jù)收集颖医,制定是否編譯機(jī)器碼位衩,但AOT在編譯期就要收集到這些。
? ??好熔萧,總結(jié)一下糖驴,總共提到的Java的源碼到機(jī)器碼的處理為三種:
? ? ? ? a.編譯為字節(jié)碼+解釋器
? ? ? ? b.編譯為字節(jié)碼+JIT+解析器
? ? ? ? c.AOT直接編譯為機(jī)器碼
? ? Java目前應(yīng)用范圍要大于C/C++,采用那種方式佛致,要看那些場景更為合適遂赠,比如,Android采用的變異后的b方式(編譯后的輸出為dex文件晌杰,運(yùn)行前要將dex文件轉(zhuǎn)化為oat格式跷睦,運(yùn)行時(shí)要處理的格式為oat格式,運(yùn)行時(shí)的指令集為基于寄存器的)
? ? 從上面對C/C++及Java編譯運(yùn)行的分析肋演,可以看出抑诸,編譯的基本原理及流程是相近的烂琴,然后加入了后期的優(yōu)化,尤其是Java蜕乡,為改善編譯及運(yùn)行效率(JIT奸绷、AOT、字節(jié)碼等)层玲,做了大量的工作号醉,至Android虛擬機(jī)(當(dāng)前文中沒細(xì)說),又根據(jù)平臺特點(diǎn)辛块,在Java的基礎(chǔ)上做了大量的改進(jìn)畔派。