java語言的“編譯期”是一段不确定的過程。由于它可能指的是前端編譯器把java檔案轉變成class位元組碼檔案的過程,也可能指的是虛拟機後端執行期間編譯器(JIT)把位元組碼轉變成機器碼的過程。
以下讨論的編譯期優化指的是javac編譯器将java檔案轉化為位元組碼的過程,而執行期間優化指的是JIT編譯器所做的優化。
編譯期優化
虛拟機設計團隊把對性能的優化集中到了後端的即時編譯器(JIT)中,這樣能夠讓那些不是由javac編譯器産生的class檔案也相同能享受到編譯器優化所帶來的優點。
可是javac做了很多針對編碼過程的優化措施來改善程式猿的編碼風格和提高編碼效率。很多新生的java文法特性,都是靠編譯器的“文法糖”來實作,而不是依賴虛拟機的底層改進來支援。
是以說,java中即時編譯器在執行期間的優化過程對于程式的執行來說更重要,而前端編譯器在編譯期的優化過程對于程式編碼來說關系更加密切。
javac編譯器的編譯過程大緻可分為三個步驟:
1.解析與填充符号表過程;
2.插入式注解處理器的注解處理過程;
3.語義分析與位元組碼生成過程。
以下分别來介紹。
解析與填充符号表;
解析步驟包括了詞法分析和文法分析兩個過程,首先詞法分析是将源碼的字元流轉變成為标記集合(token)。然後文法分析是根據token序列來構造抽象文法樹(一種用來描寫叙述程式代碼文法結構的樹狀表示方式)。完畢詞法分析和文法分析之後,下一步是填充符号表,符号表是由一組符号位址和符号資訊構成的表格,符号表中所登記的資訊在編譯的不同階段都要用到(比方語義分析中符号表所登記的内容将用于語義檢查和産生中間代碼,目标代碼生成階段當對符号名進行位址配置設定時,符号表是位址配置設定的根據)。
插入式注解處理器的注解處理過程:
插入式注解處理器能夠看做是一組編譯器的插件。在這些插件裡面。能夠讀取、改動、加入抽象文法樹中的随意元素。假設這些插件在處理注解期間對文法樹進行了改動,那麼編譯器将回到解析及填充符号表的過程又一次處理,直到全部的插入式注解處理器都沒有再對文法樹進行改動為止。
語義分析與位元組碼生成過程:
文法分析之後,編譯器獲得了程式代碼的抽象文法樹表示,文法樹可以表示結構正确的源程式的抽象,可是無法保證源程式是否符合邏輯,而語義分析主要是對結構上正确的源程式進行上下文有關性質的檢查。
1.标注檢查
标注檢查步驟檢查的内容包含諸如變量使用前是否已被聲明、變量與指派之間的資料類型是否可以比對。等等。另一個重要的動作稱為常量折疊也在此階段完畢。
2.資料及控制流分析
資料及控制流分析是對程式上下文邏輯更進一步的驗證,它能夠檢查出諸如程式局部變量在使用前是否有指派、方法的每條路徑是否有傳回值、是否全部的受查異常都被正确處理了等問題。
3.解文法糖
文法糖是指在計算機語言中加入某種文法,這樣的文法對語言的功能并沒有影響,可是更友善程式猿使用。
java中的泛型。變長參數,自己主動拆箱與裝箱,條件編譯等就屬于文法糖。它們在編譯階段就被還原成簡單的文法結構(比方List<String>和List<Integer>在執行期間事實上是同一個類)。
4.位元組碼生成
此過程是javac編譯過程的最後一個階段,位元組碼生成階段将之前各個步驟所生成的資訊轉化成位元組碼寫到磁盤中,另外還進行少量的代碼加入和轉換工作。
執行期優化
在部分商用虛拟機中。java程式最初是通過解釋器進行解釋執行的,當虛拟機發現某個方法或代碼塊執行特别頻繁,就會把這些代碼認定為“熱點代碼”,為了提高熱點代碼的執行效率,在執行時,虛拟機就會把這些代碼編譯成與本地平台相關的機器碼,并進行各種層次的優化,完畢這個任務的編譯器稱為即使編譯器或JIT編譯器。
即時編譯器并非虛拟機必須的部分,可是即時編譯器編譯性能的好壞、代碼優化程度的高低确是衡量一款商用虛拟機優秀與否的最關鍵的名額之中的一個。
衆多主流的虛拟機都同一時候包括解釋器和JIT編譯器,解釋器與JIT編譯器各有優勢:當程式須要迅速啟動和運作時,解釋器能夠首先發揮作用,省去編譯的時間,馬上運作。當程式運作後,随着事件的推移。JIT編譯器逐漸發揮作用。把越來越多的代碼編譯成本地代碼之後。能夠擷取更高的運作效率。
會被即時編譯器編譯的熱點代碼有兩類:
1.被多次調用的方法體。
2.被多次調用的循環體。
即時編譯器會以整個方法作為編譯對象。将其編譯成機器碼。
推斷一段代碼是否是熱點代碼的方式(熱點探測)有兩種:
1.基于採樣的熱點探測:
此方法會周期性檢查各個線程的棧頂。假設發現某個或某些方法常常出如今棧頂。那麼這種方法就是熱點方法。此方法的缺點是非常難精确地确認一個方法的熱度。easy受到諸如線程堵塞等因素影響。
2.基于計數器的熱點探測:
此方法會為每一個方法甚至是代碼塊建立計數器。統計方法的運作次數,假設運作次數超過一個閥值就覺得它是熱點方法。
注:預設設定下,運作引擎并不會同步等待編譯請求完畢。而是繼續進入解釋器依照解釋方式運作位元組碼,直到送出的請求被編譯器編譯完畢。當編譯工作完畢之後,這種方法的調用入口位址就會被系統自己主動改寫成新的位址,下一次調用該方法時就會使用已編譯的版本号。也就是說,在編譯器還未完畢之前,運作引擎仍依照解釋方式繼續運作。而編譯動作則在背景的編譯線程中進行。
優化技術:
一般來說即時編譯器所産生的本地代碼會比javac産生的位元組碼更優秀。即時編譯器採用了一系列的技術來優化代碼。比方公共子表達式消除,數組範圍内檢查消除。方法内聯,逃逸分析等。