程序編譯與代碼優(yōu)化

概述

編譯器是一段“不確定”的操作過程

編譯器類型

  1. 前端編譯器:將Java代碼編譯為class字節(jié)碼
  • 代表:sun公司的javac(Java語言編寫)枫绅、Eclipse JDT中的增量編譯器ECJ
  1. 后端編譯器(JIT編譯器):將字節(jié)碼轉(zhuǎn)變?yōu)闄C(jī)器碼
  • 代表: HotSpot VM的C1志珍、C2編譯器
  1. 靜態(tài)提前編譯器(AOT編譯器 Ahead of Time Compiler):將Java代碼編譯為機(jī)器碼
    -代表:GNU Compiler for the java、Excelsior JET

1. 早期(編譯期)優(yōu)化

主要說明Sun Javac的大概編譯過程

  1. 解析與填充符號表過程
    1.1 解析:
    1.1.1 詞法分析:將源代碼的字符流轉(zhuǎn)為標(biāo)記(Token)集合(例如:關(guān)鍵字塞弊,變量名宪躯,運算符笼痛,字面量)
    1.1.2 語法分析:根據(jù)Token序列構(gòu)造抽象語法樹捞奕,語法樹的每一個節(jié)點都是一個語法結(jié)構(gòu)(例如:包,類型桥狡,修飾符搅裙,運算符,接口裹芝,返回值呈宇,代碼注釋)
    1.2 符號表填充:符號表由符號地址和符號信息組成的表格(可以看成K-V鍵值對)
  2. 注解處理
    插入式注解處理器的標(biāo)準(zhǔn)API在編譯期間對注解進(jìn)行處理
  3. 分析與生成字節(jié)碼
    因為由于由語法分析所生成的抽象語法樹不能保證邏輯性,故而語義分析是對正確性的審查
int a =1;
boolean b = false;
int c = a+b;//編譯不能通過
  • 3.1 標(biāo)注檢查
    主要檢查:變量使用前是否已經(jīng)被聲明局雄、變量與賦值之間的數(shù)據(jù)類型是否能匹配
  • 3.2 數(shù)據(jù)及控制流分析
    主要檢查:對程序的上下文更進(jìn)一步的驗證,如:局部變量在使用前是否已經(jīng)賦值存炮、方法的每條路徑是否都有返回值炬搭、是否所有的受檢異常都被正確的處理等問題
//這里的兩個方法蜈漓,編譯出的字節(jié)碼沒有一點區(qū)別,只是有final修飾的不能被改變
public void test(final int a) {}
public void test(int a) {}
  • 3.3 解語法糖
    泛型擦除(實際上就是將結(jié)果強(qiáng)轉(zhuǎn))宫盔、變長參數(shù)融虽、自動裝箱/拆箱、內(nèi)部類灼芭、枚舉類有额、斷言語句、對枚舉和字符串的支持彼绷、try語句定義和關(guān)閉資源等
    條件編譯:使用條件為常量的if語句
//1. 
public static void main (String[] args) {
    if (true) {
        System.out.println("1");
    } else {
         System.out.println("2");
      }
}
//在編譯之后的結(jié)果
public static void main (String[] args) {
    System.out.println("1");   
}
//2. 下面語句將會拒絕編譯
public static void main(String[] args) {
        while (false) {
            System.out.println("錯誤");
        }
    }
  1. 字節(jié)碼生成
    字節(jié)碼生成不僅僅將前面步驟生成的信息轉(zhuǎn)換為字節(jié)碼寫到磁盤中巍佑,還要進(jìn)行少量的代碼添加和轉(zhuǎn)換工作

2. 晚期(運行期)優(yōu)化

部分商用虛擬機(jī)(Sun HotSpot、IBM J9)中寄悯,Java程序最初通過解釋器(interpreter)解釋執(zhí)行萤衰,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個方法或者代碼運行特別頻繁時,就會把這些代碼定義為“熱點代碼”猜旬。為了提高熱點代碼的執(zhí)行效率脆栋,在運行時,虛擬機(jī)會把這些代碼編譯成與本地平臺相關(guān)的機(jī)器碼洒擦,并進(jìn)行各種層次的優(yōu)化椿争。完成這個任務(wù)的編譯器就是即時編譯器(JIT just int time complier)
JRockit沒有解釋器,因此啟動時間較長

  1. 為何HotSpot 虛擬機(jī)要使用解釋器與編譯器并存的架構(gòu)熟嫩?
    兩種編譯器各有優(yōu)勢: 解釋器:啟動快秦踪、執(zhí)行快; 編譯器:執(zhí)行效率高
    Client模式啟動速度較快邦危,Server模式啟動較慢洋侨,但是啟動進(jìn)入穩(wěn)定期長期運行之后Server模式的程序運行速度比Client要快很多
  2. 為何HotSpot 虛擬機(jī)要實現(xiàn)兩個不同的即使編譯器?
    client compiler:獲取更快的編譯速度倦蚪;server compiler:獲取更好的編譯質(zhì)量希坚。具體使用哪種,虛擬機(jī)會根據(jù)自身版本和宿主機(jī)的硬件性能自由選擇陵且,當(dāng)然也可以強(qiáng)制運行某種模式裁僧,無論編譯器采用client compiler 還是server compiler,解釋器與編譯器搭配使用都稱為“混合模式(mixed mode)”慕购,也可以強(qiáng)制虛擬機(jī)運行編譯模式(-Xcomp)或者解釋模式(-Xint)
MacBook-Pro:~ shizhenshuang$ java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
MacBook-Pro:~ shizhenshuang$ java -Xint -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, interpreted mode)
MacBook-Pro:~ shizhenshuang$ java -Xcomp -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, compiled mode)
  1. 程序什么時候用解釋器執(zhí)行聊疲?什么時候用編譯器執(zhí)行?
    當(dāng)程序剛開始啟動的時候沪悲,解釋器優(yōu)先執(zhí)行获洲,省去編譯時間,當(dāng)程序運行后殿如,隨著時間的推移贡珊,編譯器逐漸發(fā)揮作用最爬,把越來越多的代碼編譯成本地代碼。那么门岔,什么時候編譯器開始執(zhí)行呢爱致?

編譯對象(熱點代碼)與觸發(fā)條件

  • 被多次調(diào)用的方法
  • 被多次調(diào)用的循環(huán)體(實際編譯的事整個方法)
    方法替換使用的是棧上替換(On Stack Replacement)

判斷一段代碼是不是熱點代碼,是否需要觸發(fā)即時編譯寒随,這樣的行為成為熱點探測糠悯。熱點探測目前流行的有兩種方法

  1. 基于采樣的熱點探測
    虛擬機(jī)周期性檢查各個線程的棧頂,如果出現(xiàn)某個(或某些)方法經(jīng)常出現(xiàn)在棧頂妻往,那就是熱點方法
  2. 基于計數(shù)器的熱點探測
    虛擬機(jī)為某個方法或者代碼塊建立計數(shù)器互艾,統(tǒng)計執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過一定的閾值就認(rèn)為它是熱點代碼蒲讯。(閾值:client模式是1500忘朝,server模式是10000,可以通過參數(shù):-XX:CompileThreshold 設(shè)置)
    計數(shù)器又分為:
    2.1 方法調(diào)用計數(shù)器
方法調(diào)用計數(shù)器觸發(fā)即時編譯

2.2 回邊計數(shù)器

回邊計數(shù)器觸發(fā)編譯條件
  1. 如何從外部觀察即時編譯器編譯過程和編譯結(jié)果判帮?
    使用debug局嘁,fastdebug版本的虛擬機(jī)(JDK6u25之后就不提供下載了),運行時晦墙,添加參數(shù)-XX:+PrintCompilation
  2. 編譯優(yōu)化項
    代表
  • 1 方法內(nèi)聯(lián):1. 除去方法調(diào)用的成本悦昵;2. 為其他優(yōu)化建立良好的基礎(chǔ)
class A {
    int age;

    public int getAge() {
        return age;
    }
}
    public static void main(String[] args) {
        A a = new A();
        int y = a.getAge();
    }
//優(yōu)化后的代碼如下(用Java代碼表示)
  public static void main(String[] args) {
        A a = new A();
        int y = a.age;
    }
  • 2 消除冗余代碼
  • 3 代碼復(fù)寫傳播
int y = 2;
z = y;
int sum = y+ z;
//復(fù)寫傳播
y=y;
int sum = y+y;
//消除冗余代碼后
int sum = y+y;

典型代表
1. 公共子表達(dá)式消除:如果一個表達(dá)式E已經(jīng)計算過了,并且從先前的計算到現(xiàn)在E中的所有變量的值都沒有改變晌畅,那么E的這次出現(xiàn)就成為了公共子表達(dá)式但指,也就沒必要再花時間對他進(jìn)行計算了(若a+b=c, 則int i = a+b+1 --> int i = c+1)
2. 數(shù)組范圍檢查消除:1. 編譯時檢查,2. 通過數(shù)據(jù)流分析
3. 方法內(nèi)聯(lián):就是把目標(biāo)方法的代碼拷貝到調(diào)用方抗楔,避免發(fā)生真實的方法調(diào)用
4. 逃逸分析(JDK1.6開始):它并不是直接優(yōu)化代碼的手段棋凳,而是為其他優(yōu)化手段提供依據(jù)的分析技術(shù)。逃逸分析的基本行為就是分析對象動態(tài)作用域连躏。當(dāng)一個對象在方法中被定義剩岳,通過參數(shù)的形式傳遞到其他方法中,稱為方法逃逸入热。甚至有可能被外部線程訪問到拍棕,譬如賦值給類變量或在其他線程中訪問實例變量,稱為線程逃逸勺良。如果能夠確定一個對象不會逃逸到方法或者線程之外绰播,則可以為這個對象做一些高效的優(yōu)化
4.1 棧上分配:將對象分配在棧上,內(nèi)存空間隨著棧幀出棧而銷毀尚困,減少gc回收的壓力
4.2 同步消除:如果確定一個對象不會逃逸出線程蠢箩,就無須對這個對象實施通過的措施(線程同步是一個相對耗時的過程)
4.3 標(biāo)量替換:標(biāo)量是指不能再拆解的數(shù)據(jù)類型纤控,如原始數(shù)據(jù)類型厉斟。如果一個數(shù)據(jù)還能被分解商蕴,它就稱為聚合量蒿柳,如對象径缅。標(biāo)量替換就是將對象的成員變量替換為原始的數(shù)據(jù)類型粘拾。逃逸分析證明一個對象不會被外部訪問撒犀,并且這個對象可以被拆解伶丐,那么程序執(zhí)行的時候可能就不會真正的創(chuàng)建這個對象跨跨,而是創(chuàng)建它的若干個被這個方法訪問的成員變量

  1. Java與C++的編譯器對比
    即:即時編譯器與靜態(tài)編譯器的對比
    1. 即時編譯器占用的是用戶運行的時間潮峦,具有很大的時間壓力。而編譯時間成本在靜態(tài)編譯器中并不是主要關(guān)注點
    1. Java語言是動態(tài)類型安全語言勇婴。這就意味著由虛擬機(jī)來確保程序不會違反語義和非結(jié)構(gòu)化內(nèi)存忱嘹,這就使得虛擬機(jī)得頻繁的動態(tài)檢查空指針,數(shù)組下標(biāo)范圍耕渴,類型轉(zhuǎn)換等
    1. Java中雖然沒有virtual關(guān)鍵字拘悦,但是接受者進(jìn)行動態(tài)選擇的頻率要遠(yuǎn)遠(yuǎn)大于C/C++語言,優(yōu)化難度要大于靜態(tài)編譯器
    1. Java語言是動態(tài)擴(kuò)展語言橱脸,運行時加載新的類可能會改變程序類型的繼承關(guān)系
    1. Java語言中的對象都是在堆上分配础米,只有方法中的局部變量才在棧上分配。而C/C++則有多種內(nèi)存分配添诉,既可以在堆上屁桑,也可以在棧上。C/C++主要是用戶程序代碼回收內(nèi)存分配栏赴,因此效率上要高于Java
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蘑斧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子须眷,更是在濱河造成了極大的恐慌竖瘾,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件花颗,死亡現(xiàn)場離奇詭異捕传,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捎稚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門乐横,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人今野,你說我怎么就攤上這事葡公。” “怎么了条霜?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵催什,是天一觀的道長。 經(jīng)常有香客問我宰睡,道長蒲凶,這世上最難降的妖魔是什么气筋? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮旋圆,結(jié)果婚禮上宠默,老公的妹妹穿的比我還像新娘。我一直安慰自己灵巧,他們只是感情好搀矫,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著刻肄,像睡著了一般瓤球。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上敏弃,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天卦羡,我揣著相機(jī)與錄音,去河邊找鬼麦到。 笑死绿饵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隅要。 我是一名探鬼主播蝴罪,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼步清!你這毒婦竟也來了要门?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤廓啊,失蹤者是張志新(化名)和其女友劉穎欢搜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谴轮,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡炒瘟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了第步。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疮装。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粘都,靈堂內(nèi)的尸體忽然破棺而出廓推,到底是詐尸還是另有隱情,我是刑警寧澤翩隧,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布樊展,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏专缠。R本人自食惡果不足惜雷酪,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涝婉。 院中可真熱鬧哥力,春花似錦、人聲如沸墩弯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽最住。三九已至,卻和暖如春怠惶,著一層夾襖步出監(jiān)牢的瞬間涨缚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工策治, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留脓魏,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓通惫,卻偏偏與公主長得像茂翔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子履腋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351