
前言
這兩天重溫了周志明的《深入理解Java虛擬機(jī)》第2版鸳粉,發(fā)現(xiàn)第11章第4節(jié)關(guān)于 Java 編譯器的內(nèi)容寫得太棒了~本文完全摘自《深入理解Java虛擬機(jī)》第2版 0_o
Java 與 C/C++ 編譯器
大多數(shù)程序員都認(rèn)為C/C++會比Java語言快绽媒,甚至覺得從Java語言誕生以來“執(zhí)行速度緩慢”的帽子就應(yīng)當(dāng)扣在它的頭頂李请,這種觀點(diǎn)的出現(xiàn)是由于Java剛出現(xiàn)的時候即時編譯技術(shù)還不成熟,主要靠解釋器執(zhí)行的Java語言性能確實比較低下
。但目前即時編譯技術(shù)已經(jīng)十分成熟,Java語言有可能在速度上與C/C++一爭高下嗎绝编?要想知道這個問題的答案,讓我們從兩者的編譯器談起。
Java與C/C++的編譯器對比十饥,實際上代表了最經(jīng)典的即時編譯器
與靜態(tài)編譯器
的對比窟勃,很大程度上也決定了Java與C/C++的性能對比結(jié)果,因為無論是C/C++還是Java代碼逗堵,最終編譯之后被機(jī)器執(zhí)行的都是本地機(jī)器碼秉氧,哪種語言的性能更高,除了它們自身的API庫實現(xiàn)地好壞
之外蜒秤,其余的比較就成了一場拼編譯器
的游戲汁咏。當(dāng)然,這種比較也是剔除了開發(fā)效率的片面對比作媚,語言孰優(yōu)孰劣攘滩、誰快誰慢的問題都是很難有結(jié)果的爭論,下面我們就回到正題掂骏,看看這兩種語言的編譯器各有何優(yōu)勢轰驳。
Java 編譯器“劣勢”的原因
Java虛擬機(jī)的即時編譯器與C/C++的靜態(tài)優(yōu)化編譯器相比厚掷,可能會由于下列原因霍掺,而導(dǎo)致輸出的本地代碼有一些劣勢(下面列舉的也包括一些虛擬機(jī)執(zhí)行子系統(tǒng)的性能劣勢):
第一逃魄,因為即時編譯器運(yùn)行占用的是用戶程序的運(yùn)行時間
,具有很大的時間壓力,它能提供的優(yōu)化手段也嚴(yán)重受制于編譯成本拉讯。如果編譯速度達(dá)不到要求,那用戶將在啟動程序或程序的某部分察覺到重大延遲挪圾,這點(diǎn)使得即時編譯器不敢隨便引入大規(guī)模的優(yōu)化技術(shù)潘靖,而編譯的時間成本在靜態(tài)優(yōu)化編譯器中并不是主要的關(guān)注點(diǎn)。
第二冬竟,Java語言是動態(tài)的類型安全語言
欧穴,這就意味著需要由虛擬機(jī)來確保程序不會違反語言語義或訪問非結(jié)構(gòu)化內(nèi)存。從實現(xiàn)層面上看泵殴,這就意味著虛擬機(jī)必須頻繁地進(jìn)行動態(tài)檢查
涮帘,如實例方法訪問時檢查空指針、數(shù)組元素訪問時檢查上下界范圍笑诅、類型轉(zhuǎn)換時檢查繼承關(guān)系等调缨。對于這類程序代碼沒有明確寫出的檢查行為,盡管編譯器會努力進(jìn)行優(yōu)化吆你,但是總體上仍然要消耗不少的運(yùn)行時間弦叶。
第三,Java語言中雖然沒有virtual關(guān)鍵字妇多,但是使用虛方法的頻率卻遠(yuǎn)遠(yuǎn)大于C/C++語言
伤哺,這意味著運(yùn)行時對方法接收者進(jìn)行多態(tài)選擇的頻率要遠(yuǎn)遠(yuǎn)大于C/C++語言,也意味著即時編譯器在進(jìn)行一些優(yōu)化(如方法內(nèi)聯(lián))時的難度要遠(yuǎn)遠(yuǎn)大于C/C++的靜態(tài)優(yōu)化編譯器。
第四默责,Java語言是可以動態(tài)擴(kuò)展
的語言贬循,運(yùn)行時加載新的類可能改變程序類型的繼承關(guān)系
,這使得很多全局的優(yōu)化難以進(jìn)行
桃序,因為編譯器無法看清程序的全貌杖虾,許多全局的優(yōu)化都只能以激進(jìn)優(yōu)化的方式來完成,編譯器不得不時刻注意并隨著類型的變化而在運(yùn)行時撤銷或重新進(jìn)行一些優(yōu)化媒熊。
第五奇适,Java語言的對象內(nèi)存是在堆上
,只有方法的局部變量才能在棧上分配芦鳍,而C/C++的對象則有多重內(nèi)存分配方式嚷往,既可能在堆上分配,又可能在棧上分配柠衅,如果可以在棧上分配線程私有的對象皮仁,將減輕內(nèi)存回收的壓力。另外菲宴,C/C++中主要由用戶用程序代碼來回收分配的內(nèi)存贷祈,這就不存在無用對象篩選的過程,因此效率上(僅是運(yùn)行效率喝峦,排除開發(fā)效率)也比Java的垃圾收集機(jī)制要高势誊。
Java 編譯器的“優(yōu)勢”
上面所了一堆Java語言在性能上的劣勢,這些都是為了換取「開發(fā)效率」上的優(yōu)勢而付出的代價谣蠢,動態(tài)安全粟耻、動態(tài)擴(kuò)展、垃圾回收這些“拖后腿”的特性眉踱,都為Java語言的開發(fā)效率做出了很大貢獻(xiàn)挤忙。
何況,還有許多優(yōu)化是Java的即時編譯器能做谈喳,而C/C++的靜態(tài)優(yōu)化編譯器不能做或者不好做的册烈。例如,在C/C++中叁执,別名分析(Alias Analysis)的難度就要遠(yuǎn)遠(yuǎn)高于Java茄厘。Java的類型安全保證了在類似如下代碼中,只要ClassA和ClassB沒有繼承關(guān)系谈宛,那對象objA和objB就絕不可能是同一個對象次哈,即不會是同一塊內(nèi)存兩個不同別名。
void foo(ClassA objA, ClassB objB) {
objA.x = 123;
objB.y = 456;
// 只要objB.y不是objA.x的別名吆录,下面就可以保證輸出為123
print(objA.x);
}
確定了objA和objB并非對方的別名后窑滞,許多與數(shù)據(jù)依賴相關(guān)的優(yōu)化才可以進(jìn)行(重排序、變量替換)。具體到這個例子中哀卫,就是無需擔(dān)心objB.y與objA.x指向同一塊內(nèi)存巨坊,這樣就可以安全地確定打印語句中的objA.x為123。
Java編譯器另外一個紅利是由它的動態(tài)性所帶來的此改,由于C/C++編譯器所有優(yōu)化都在編譯期完成趾撵,以運(yùn)行期性能監(jiān)控為基礎(chǔ)的優(yōu)化措施
它都無法進(jìn)行,如:
- 調(diào)用頻率預(yù)測:Call Frequency Prediction
- 分支頻率預(yù)測:Branch Frequency Prediction
- 裁剪未被選擇的分支:UNtaken Branch Pruning
這些都是Java語言獨(dú)有的性能優(yōu)勢共啃。
總結(jié)
隨著Java JIT編譯技術(shù)的發(fā)展占调,Java的運(yùn)行速度已經(jīng)足夠快。Java能夠在運(yùn)行時動態(tài)加載類(可以從zip包移剪、網(wǎng)絡(luò)究珊、運(yùn)行時計算、其他文件生成)纵苛,C/C++則完全做不到這一點(diǎn)剿涮。總的來說攻人,Java的動態(tài)安全取试、動態(tài)擴(kuò)展、垃圾回收等特性贝椿,使得開發(fā)效率很高想括,并且足夠靈活陷谱;同時隨著編譯技術(shù)的不斷發(fā)展烙博,性能的劣勢正在逐漸減小。