執行引擎(三):程序編譯與代碼優化
- 早期(編譯期)優化
- java語言編譯期
- 1.前端編譯器(編譯器的前端):*.java文件 => .class文件
- Sun的Javac,Eclipse_JDT中的增量式編譯器(ECJ)
- 2.後端運行期編譯器(JIT編譯器):字節碼轉 => 機器碼
- HotSpot_VM的C1、C2編譯器
- 3.靜態提前編譯器(AOT編譯器):.java文件 => 本地機器代碼
- CNU_Compilr_for_the_Java(GCJ)、Excelsior_JET
- CNU_Compilr_for_the_Java(GCJ)、Excelsior_JET
- 註:虛擬機設計:性能優化集中到後端即時編譯期中
- 抽象優化;class文件受益(JRuby、Groovy等語言的Class文件等)
- 抽象優化;class文件受益(JRuby、Groovy等語言的Class文件等)
- 1.前端編譯器(編譯器的前端):*.java文件 => .class文件
- Javac編譯器
- Javac編譯過程
- 1.解析與填充符號表過程
- 詞法、語法分析
- 詞法分析
- 概念:源代碼的字符流 => 標記集合
- 單個字符:程序編寫過程最小元素
- 標記:即編譯過程的最小元素(關鍵字,變量名,字面量,運算符等)
- 由com.sun.tools.javac.parser.Scanner類實現
- 由com.sun.tools.javac.parser.Scanner類實現
- 單個字符:程序編寫過程最小元素
- 概念:源代碼的字符流 => 標記集合
- 語法分析
- 概念:根據Token序列構造抽象語法樹
- 抽象語法樹:樹形標識描述程序代碼語法結構
- 語法樹節點:代表程序語法結構。
- 如包、類型、修飾符、運算符、接口、返回值、註釋等
- 註:編譯器經過語法分析後,不會對源碼文件進行操作,後續操作建立在抽象語法樹上
- 如包、類型、修飾符、運算符、接口、返回值、註釋等
- 由com.sun.tools.javac.parser.Parser類實現
- 由com.sub.tools.javac.tree.JCTree類表示
- 語法樹節點:代表程序語法結構。
- 抽象語法樹:樹形標識描述程序代碼語法結構
- 概念:根據Token序列構造抽象語法樹
- 詞法分析
- 填充符號表
- 符號表:符號地址:符號信息(K-V形式)表格
- 編譯不同階段使用
- 語義分析中,符號表信息用於語義檢查(檢查一個名字的使用和原先的說明是否一致) ,產生中間代碼
- 目標代碼生成階段:符號表:對符號名 進行地址分配時依據
- 由com.sun.tools.javac.comp.Enter類實現:
- 返回待處理表(包含編譯單元的抽象語法樹,package-info.java)的頂級節點)
- 編譯不同階段使用
- 符號表:符號地址:符號信息(K-V形式)表格
- 詞法、語法分析
- 2.插入式註解處理器的註解處理過程
- 註解處理器:編譯期間對註解進行處理(讀取,修改,添加 抽象語法樹中的任意元素)
- JDK1.5之後提供了對註解的支持,運行期間發揮作用。
- JDK1.6之後提供了插入式註解處理器標準API,
- JDK1.5之後提供了對註解的支持,運行期間發揮作用。
- 註:如插入式註解處理器在處理註解期間對語法樹進行了修改,編譯器將重新填充符號表過程
- 直到所有的插入式註解處理器都沒有再對語法樹進行修改為止
- 初始化過程在initProcessAnnotations()方法中完成
- 執行過程在processAnnotation()方法中完成:
- 判斷是否還有新的註解處理器需要執行
- 如果有,通過com.sun,tools.javac.processing.JacacProcessingEnvironment類的doProcessing()方法生成一個新JavaCompiler對象對編譯的後續步驟進行處理
- 直到所有的插入式註解處理器都沒有再對語法樹進行修改為止
- 註解處理器:編譯期間對註解進行處理(讀取,修改,添加 抽象語法樹中的任意元素)
- 3.分析與字節碼生成過程
- 語義分析與字節碼生成
- 語義分析的任務:
- 對結構上進行上下文審查:
- 類型審查等
- 語義分析
- 1.標註檢查
- 內容:檢查變量使用前聲明,變量賦值數據類型匹配
- 常量折疊:如int a = 1 + 2, 經過常量折疊後會被折疊為字面量3,
- 即代碼裏定義a=1+2比起a=3並不會增加程序運行期的運算量
- 由com.sun.tools.javac.comp.Attr
- com.sun.tools.javc.comp.Check類實現
- 即代碼裏定義a=1+2比起a=3並不會增加程序運行期的運算量
- 內容:檢查變量使用前聲明,變量賦值數據類型匹配
- 2.數據及控制分析
- 內容:檢查局部變量使用前賦值,方法返回值受查異常正確處理等
- 如:將布局變量聲明為final,對運行期無影響,不變性編譯期間保障,
- 局部變量在常量池中沒有CONSTANT_Fieldref_info的符號引用,即沒有訪問標誌的信息,可能名稱都保存(取決於編譯時的選項)
- 如:將布局變量聲明為final,對運行期無影響,不變性編譯期間保障,
- 由com.sun.tools.javac.comp.Flow類來完成
- 內容:檢查局部變量使用前賦值,方法返回值受查異常正確處理等
- 3.解語法糖
- 語法糖:指在計算機語言中添加的某種語法,對語言的功能並無影響,但是更方便程序員使用
- 如:泛型,編程參數,自動裝箱/拆箱等
- 如:泛型,編程參數,自動裝箱/拆箱等
- 註:虛擬機運行時不支持這些語法,編譯階段還原會最基礎語法結構,即解語法糖
- 語法糖:指在計算機語言中添加的某種語法,對語言的功能並無影響,但是更方便程序員使用
- Java語法糖的味道
- 泛型與類型擦除
- JDK1.5的一項新增特性
- 本質:參數化類型的應用。
- 即所操作的數據類型被指定為一個參數,
- 可用在類、接口、和方法創建中,稱為泛型類,泛型接口和泛型方法
- 即所操作的數據類型被指定為一個參數,
- C#和Java中的泛型
- C#泛型:類型膨脹
- C#泛型 程序源碼、編譯後的IL(中間語言,這時候的泛型是一個占位符)運行期的CLR中 確切存在
- List<String>和List<Integer>就是兩種不同的類型,系統運行期生成,有各自的虛方法表和類型數據,
- 基於類型膨脹實現的泛型稱為真實泛型
- Java泛型:
- Java泛型只存在程序源碼中
- 編譯後的字節碼文件中 已替換為 原來的原生類型(裸類型),且相應的地方插入了強制轉型
- 運行期的java語言 List<int>和List<String>就是同一個類
- 泛型擦除前
- 泛型擦除後:編譯為Class文件,然後反編譯後
- Java泛型只存在程序源碼中
- JDK1.6中泛型的問題
- 自動裝箱,拆箱與遍歷循環
- 自動裝箱、拆箱與遍歷循環前
- 自動裝箱、拆箱與遍歷循環後:編譯為Class文件,然後反編譯後
- 註:遍歷循環即還原了叠代器的實現。即實現Iterable接口原因
- 自動裝箱的陷阱:
- JDK包裝類 == 運算在不遇到 算數運算符 情況 不會 自動拆箱
- equals()方法不處理數據轉型
- 自動裝箱,拆箱與遍歷循環
- C#泛型:類型膨脹
- 條件編譯
- java語言的條件編譯本質:語法糖 根據布爾把分支中不成立的代碼塊消除掉。編譯器接觸語法糖階段完成。
- 智能實現語句基本塊級別的條件編譯,而沒有辦法實現根據條件調整整個java類的結構
- 條件編譯前
- 條件編譯後:編譯為Class文件,然後反編譯後
- 泛型與類型擦除
- 4.字節碼生成
- 字節碼生成:前面生成信息(語法樹,符號表) => 字節碼 寫到磁盤
- 編譯器進行少量代碼添加,轉換工作
- 編譯器進行少量代碼添加,轉換工作
- 生成構造器
- 如:實例構造器init方法和類構造器clinit方法
- 產生過程本質:代碼收斂的過程
- 編譯器 把語句塊、變量初始化、調用父類的實例構造器 等操作 收斂到 實例構造器和 類構造器方法之中
- 且保證按 執行父類的實例構造器,初始化變量,執行語句塊的順序進行
- 由Gen.normalizeDefs()方法來實現
- 註:該實例構造器並不是指默認構造器,
- 如用戶代碼中無構造函數,編譯器將會添加無參,訪問性與當前類一致的 默認構造函數(填充符號表階段就已經完成)
- 如:實例構造器init方法和類構造器clinit方法
- 代碼替換
- 用於優化程序的實現邏輯:
- 如:把字符串的加操作替換為 StrngBuffer或 StringBuider(取決於目標代碼的版本是否大於或等於JDK1.5)append()操作等
- 語法樹遍歷調整完成後,會把填充了所有所需信息的符號表交給
- com.sin.toos.javac.jvm.ClassWtiter類的writeClass()方法輸出字節碼生成最終Class文件結束
- 由com.sun.tools.javac.jvm.Gen類來完成
- 用於優化程序的實現邏輯:
- 字節碼生成:前面生成信息(語法樹,符號表) => 字節碼 寫到磁盤
- 1.標註檢查
- 語義分析的任務:
- 語義分析與字節碼生成
- 1.解析與填充符號表過程
- Javac編譯過程
晚期(運行期)優化
-
概述:
-
Java程序最初 通過解釋器 進行解釋執行的。
-
熱點代碼:某個方法或代碼塊的運行頻繁
-
即時編譯器:運行時,熱點代碼編譯成 本地平臺相關的機器碼。並進行優化
-
衡量一款商用虛擬機優秀(虛擬機技術水平體現):
- 註:即時編譯期編譯性能的好壞,代碼優化程度的高低
-
HotSpot虛擬機內的即時編譯器
-
解釋器與編譯器
-
優點:
-
編譯器
-
程序運行後,代碼 => 本地代碼 隨時間成正比
-
分類
-
C1:Client Compiler
-
強制指定虛擬機運行在Client模式:-client
-
-
C2:Server Compiler
-
強制指定虛擬機運行在Server模式:-server
-
-
-
-
解釋器
-
不需編譯。立即執行
-
逆優化:編譯器激進優化容災
-
激進優化不成立(如加載了新類後類型繼承結構出現變化,出現罕見陷阱) 可通過逆優化 回滾 解釋狀態繼續執行
-
-
程序運行環境內存資源限制較大,使用解釋執行 節約內存
-
程序運行環境內存資源限制較小,使用編譯執行 提升效率
-
工作方式
-
混合模式:默認解釋器與C1或C2編譯器配合工作。
-
取決虛擬機運行的模式
-
根據自身版本 宿主機 硬件性能 自動選擇運行模式
-
-
解釋模式:全部 使用 解釋方式 執行
-
強制指定虛擬機運行與解釋模式:-Xint
-
-
編譯模式:優先采用 編譯方式 執行
-
解釋器 在 編譯無法進行 情況 介入執行過程
-
強制指定虛擬機運行與編譯模式:-Xcomp
-
-
-
-
分層編譯
-
為什麽分層編譯?
-
即時編譯器 編譯本地 代碼 需占用程序運行時間
-
編譯 優化程序更高 代碼 時間長
-
解釋器 可能替 編譯器 收集性能監控信息
-
-
原理:根據編譯器編譯。優化的規模與耗時
-
包括
-
0層:
-
程序解釋執行,解釋器不開啟性能監控,可觸發第一層編譯
-
1層:(C1編譯)
-
將字節碼編譯為本地代碼,進行簡單,可靠的優化
-
如果有必要加入性能監控的邏輯
-
-
2層或2層以上(C2編譯)
-
將字節碼編譯為本地代碼,
-
啟用編譯耗時優化
-
性能監控信息進行不可靠激進優化
-
-
-
編譯對象與觸發條件
-
運行時即時編譯器編譯的熱點代碼
-
包括
-
-
被多次調用的方法
-
即一個方法被調用多次,
-
方法體內的代碼執行多次
-
JIT編譯方式
-
由方法調用觸發的編譯
-
編譯器會以整個方法作為編譯對象
-
被多次執行的循環體
-
-
-
即一個方法只被調用過一次或者幾次,
-
-
但是方法體內部存在循環次數較多的循環體
-
棧上替換(OSR)
-
由循環體觸發的編譯,
-
-
編譯器會以整個方法(而不是單獨的循環體)作為編譯對象
-
這種編譯方式發生在方法執行過程之中
-
熱點探測
-
-
判斷一端代碼是不是熱點代碼
-
是不是需要觸發即時編譯
-
這種行為即為熱點探測
-
包括
-
-
基於采樣的熱點探測
-
虛擬機會周期性的檢查各個線程的棧頂
-
如果發現某個(或某些)方法經常出現在棧頂
-
即為熱點方法
-
優點
-
-
-
實現簡單,高效
-
很容易獲取方法調用關系(將調用堆棧展開即可)
-
缺點
-
很難精確地確認一個方法的熱度
-
容易受到線程阻塞或別的外界因素的影響
-
從而擾亂熱點探測
-
HotStop使用:
-
基於計數器的熱點探測
-
虛擬機會為每個方法(甚至是代碼塊)建立計數器
-
統計方法的執行次數,
-
如果執行次數超過閾值
-
即為熱點方法
-
優點
-
統計結果相對來說更加精確和嚴謹
-
缺點
-
實現復雜,需要為每個方法建立並維護計數器
-
不能 直接獲取到方法的調用關系
-
包括
-
方法調用計數器
-
默認閾值:
-
Client模式:1500次
-
Server模式:10000次
-
通過-XX:CompileThreshold指定
-
運行流程
-
1.當一個方法被調用,首先檢查該方法是否存在被JIT編譯過的版本
-
2.1存在:優先使用編譯後的本地代碼來執行
-
2.2不存在,將該方法的調用計數器值+1
-
2.2.1判斷方法調用計數器與回邊計數器值之和是否超過方法調用計數器的閾值
-
2.2.2如超過,則向即時編譯器提交該方法代碼編譯的請求
-
註“如不做任何設置。執行引擎並不會等待編譯請求完成
-
而是繼續進入解釋器按照解釋方式執行字節碼
-
當提交的請求被編譯器編譯完成
-
該方法的調用入口地址會被系統自動改寫成新的
-
下一次調用該方法時就會使用已編譯的版本
-
方法調用計數器熱度的衰減
-
如不做任何設置,方法調用計數器統計的並不是方法被調用的絕對次數
-
而是一個相對的執行頻率(即一段時間內方法被調用的次數)
-
當超過一定時間限制。如果方法的調用次數仍然不足以讓他提交給即時編譯器編譯
-
則該方法的調用計數器就會被減少一半???ā?苉
-
方法統計半衰周期
-
即方法調用計數器熱度衰減的時間
-
關閉熱度衰減
-
-XX:UseCounterDecay
-
設置半衰周期時間(單位秒)
-
-XX:CounterHafLifeTime
-
回邊計數器
-
作用
-
統計一個方法中循環體代碼執行的次數(回邊次數)
-
建立回邊計數器統計的目的就是為了觸發OSR編譯
-
設置回邊計數器的閾值
-
設置方法調用計數器閾值:-XX:CompileThreshord
-
計算公式
-
虛擬機運行在Client模式
-
方法調用計數器閾值(CompileThreshold)×OSR比率(OnstackReplacePercentage)/100
-
OSR比率:默認966
-
回邊計數器閾值:默認13995
-
虛擬機運行在Server模式
-
方法調用計數器閾值)×(OSR比率-解釋器監控比率)/100
-
OSR比率:默認140
-
解釋器監控比率:默認33
-
回邊計數器閾值:默認10700
-
運行過程
-
1.當虛擬機遇到一條回邊指令時,首先查找將要執行的代碼片段是否有已經編譯好的版本
-
2.1如果有,將會優先執行已編譯好的代碼
-
2.2如果沒有,則把回邊計數器的值加1
-
2.2.1判斷放到調用計數器與回邊計數器值之和是否超過回邊計數器的閾值
-
2.2.2如果超過,則提交一個OSR的編譯請求,並且把回邊計數器的值降低
-
以便等待編譯器輸出編譯結果
-
註:回邊計數器沒有技術熱度衰減,因此這個計數器統計的就是該方法循環執行的絕對次數
-
編譯過程
-
在默認設置下,虛擬機在代碼編譯器還未完成之前,都仍然將按照解釋方式繼續執行
-
而編譯動作則在後臺的編譯線程中執行
-
禁止後臺編譯後,執行線程向虛擬機提交編譯請求後將會一直等待
-
直到編譯過程完成後在開始執行編譯器輸出的本地代碼
-
設置虛擬機禁止後臺編譯:-XX:BackgroundCompliation
-
Client Compiler
-
簡單快速的三段式編譯器,
-
主要關註局部性的優化,
-
而放棄了許多耗時較長的全局優化手段
-
第一階段:前端(平臺獨立)將字節碼構造成高級中間代碼表示HIR)
-
靜態單分配代表代碼值
-
可以使得在HIR的構造過程中和後進行的優化動作更容易實現
-
在此之前編譯器會在字節碼上完成一部分優化:方法內聯,常量傳播等
-
第二階段:後端(平臺無關)從HIR中產生一種低級中間代碼表示(LIR)
-
在此之前會在HIR上完成一些優化,
-
如:空值檢查消除,範圍檢查消除,使HIR標識達到更高效
-
第三階段:後端(平臺相關)使用線性掃描算法在LIR上分配寄存器,
-
並做窺孔優化,產生機器代碼
-
Server Compiler
-
面向服務端的典型應用,為服務端的性能配置特別調整過的編譯器,充分優化過的高級編譯器
-
執行所有的經典優化動作
-
如:無用代碼剔除,循環表達式外提,消除鞏固子表達,常量傳播,基本塊重排序
-
實施與java語言特性相關的優化技術
-
如:範圍檢查消除,空值檢查消除
-
根據解釋器或Client Compiler提供的性能監控信息,進行不穩定激進優化
-
如:守護內斂,分支頻率預測等
-
ServerCompiler的寄存器分配器是一個全局著色分配器,
-
它可以充分利用某些處理器架構上的大寄存器集合
-
編譯優化技術
-
優化技術概覽
-
優化前原始代碼
-
內聯後代碼
-
冗余存儲消除的代碼
-
復寫傳播的代碼
-
進行無用代碼消除的代碼
-
公共子表達式消除:
-
語言無關的經典優化技術之一
-
公共子表達式消除是一個普遍應用與各種編譯器的經典優化技術
-
全局公共子表達式消除
-
概念
-
優化的範圍涵蓋了多個基本塊
-
局部公共子表達式消除
-
概念
-
優化僅限於程序的基本塊內
-
概念
-
如果一個表達式E已經計算過了,並且從先前的計算到現在E中的所有變量的值都沒有發生變化,那麽E即為公共子表達式
-
例
-
javac編譯後
-
未做任何優化
-
當這段代碼進入到虛擬機即時編譯器後,它將進行如下優化:
-
編譯器檢測到c*b與b*c”是一樣的表達式,而且在計算期間b與c的值是不變的
-
編譯器還可能(取決於哪種虛擬機的編譯器以及具體的上下文而定)進行另外一種優化:
-
代數化簡,把表達式變為
-
實現
-
對於公共子表達式,沒有必要花時間再對它進行計算,只需要直接用前面計算過的表達式結果替代E就可以了
-
數組範圍檢查消除:
-
語言相關的經典優化技術之一
-
數組邊界檢查消除是即時編譯器中的一項語言相關的經典優化技術
-
原因
-
由於java語言中訪問數組元素時,系統將會自動進行上下界的範圍檢查,這必定會造成性能負擔。
-
為了安全,數組邊界檢查是必須做的,
-
但數組邊界檢查是否必須一次不漏的執行則是可以“商量”的事情。
-
實現
-
如編譯器通過數據流分析判定數組下標的取值永遠在[0,數組.length)之內,
-
就可以把數組的上下界檢查消除
-
從更高的角度看,大量安全檢查使編寫java程序更簡單,但也造成了更多的隱式開銷,
-
對於這些隱式開銷,除了盡可能把運行期檢查提到編譯期完成的思路之外,還可以使用隱式異常處理
-
虛擬機會註冊一個SegmentFault信號的異常處理器(uncommon_trap()),
-
這樣x不為空時,不會額外消耗一次對foo判空的開銷。
-
這個過程必須從用戶態轉到內核態中處理,結束後再回到用戶態,速度遠比一次判空檢查慢。
-
foo極少為空的時候,隱式異常優化是值得的,但假如foo經常為空的話,這樣的優化反而會讓程序更慢,
-
還好HotSpot虛擬機足夠“聰明”,它會根據運行期收集到的Profile信息自動選擇最優方案。
-
方法內聯:
-
最重要的優化技術之一
-
作用
-
消除方法調用的成本
-
為其他優化手段建立良好的基礎
-
虛方法內聯問題
-
虛方法可能存在多余一個版本的接受者(最多在去除被final修飾的方法)
-
即java語言中默認的實例方法是虛方法
-
解決方法
-
類型繼承關系分析(CHA)
-
基於整個應用程序的類型分析技術,
-
用於確定在目前已加載的類中,某個接口是否有多於一種的實現
-
某個類是否存在子類,子類是否為抽象類等信息
-
只有一個
-
守護內聯
-
可以進行內聯,不過這種內聯屬於激進優化,需要預留一個“逃生門”,稱為守護內聯(GuardedInlining)
-
如果程序的後續執行過程中,虛擬機一直沒有加載到會令這個方法的接收者的繼承關系發生變化的類,
-
那這個內聯遊湖的代碼就可以一直使用下去。
-
否則,就需要拋棄已經編譯的代碼,退回到解釋狀態執行,或者重新進行編譯
-
多個方法
-
內聯緩存
-
工作原理
-
1.在未發生方法調用之前,內聯緩存狀態為空
-
2.當第一次調用發生後,緩存記錄下方法接收者的版本信息
-
3.並且每次進行方法調用時都比較接收者版本
-
4.1如果以後進來的每次調用的方法接收者版本都是一樣的,那這個內聯還可以一直用下去。
-
4.2如果發生了方法接收者不一致的情況,就說明程序真正使用了虛方法的多態特性,這時才會取消內聯,查找虛方法表進行方法分派
-
註:在許多情況下虛擬機進行的內聯都是一種激進優化,激進優化的手段在高性能的商用虛擬機中很常見,
-
除了內聯之外,
-
對於出現概率很小(通過經驗數據或解釋器收集到的性能監控信息確定概率大小)的隱式異常,
-
使用率很大的分支等都可以被激進優化移除
-
如果真的出現了小概率事件,這時才會從逃生門回到解釋狀態重新執行
-
非虛方法
-
直接進行內聯,這時候的內聯是有穩定前提保障的
-
逃逸分析:
-
最前沿的優化技術之一
-
作用
-
為其他優化手段提供依據的分析技術
-
基本行為
-
分析對象動態作用域
-
方法逃逸
-
當一個對象在方法中被定義後,他可能被外部方法引用(作為調用參數傳遞到其他方法中)
-
線程逃逸
-
當一個對象在方法中被定義後,他可能被外部線程訪問到(賦值給類變量或可以在其他線程中訪問到的實例變量)
-
如果一個對象不會逃逸到方法或線程外
-
(別的方法或者線程無法通過任何途徑訪問到這個對象)
-
則可能為這個變量進行一些高效的優化
-
棧上分配(Stack Allocation)
-
原因
-
Java堆中的對象對於各個線程都是共享和可見的,只要持有這個對象的引用,就可以訪問堆中存儲的對象數據。
-
虛擬機的垃圾收集系統可以回收堆中不再使用的對象,但回收動作無論是篩選可回收對象,還是回收和整理內存都需要耗費時間。
-
實現
-
將對象在棧上分配內存,這樣就可以使對象所占內存空間隨棧幀出棧而銷毀,減小垃圾收集系統的壓力
-
註:HotSpot虛擬機目前的實現方式導致棧上分配實現起來比較復雜,因此在HotSpot中暫時還沒有做這項優化
-
同步消除(Synchronization Elimination)
-
對象無法被其他線程訪問,這個變量的讀寫肯定不會有競爭,對這個變量實施的同步措施也就可以消除掉
-
標量替換(Scalar Replacement)
-
標量
-
指一個數據已經無法再分解成更小的數據來表示
-
例
-
Java虛擬機中的原始數據類型(int、long等數值類型以及reference類型等)
-
聚合量
-
如果一個數據可以繼續分解
-
概念
-
把一個Java對象拆散,根據程序訪問的情況,將其使用到的成員變量恢復原始類型來訪問
-
實現
-
如果逃逸分析證明一個對象不會被外部訪問,並且這個對象可以被拆散的話,
-
那程序真正執行的時候將可能不創建這個對象,而改為直接創建它的若幹個被這個方法使用到的成員變量來代替。
-
優點
-
可以讓對象的成員變量在棧上(棧上存儲的數據,有很大的概率會被虛擬機分配至物理機器的高速寄存器中存儲)分配和讀寫
-
可以為後續進一步的優化手段創建條件
-
缺點
-
主要是不能保證逃逸分析的性能收益必定高於它的消耗。
-
如果要完全準確地判斷一個對象是否會逃逸,需要進行數據流敏感的一系列復雜分析,從而確定程序各個分支執行時對此對象的影響。 這是一個相對高耗時
-
的過程
-
如果分析完後發現沒有幾個不逃逸的對象,那這些運行期耗用的時間就白白浪費了
-
JVM設置參數
-
開啟逃逸分析:-XX:+DoEscapeAnalysis
-
查看分析結果:-XX:+PrintEscapeAnalysis
-
開啟標量替換:-XX:+EliminateAllocations
-
查看標量的替換情況:-XX:+PrintEliminateAllocations
-
開啟同步消除:+XX:+EliminateLocks
-
java與C/C++編譯器對比
-
java:即時編譯期
-
1.即時編譯器運行時占用的是用戶程序的運行時間,因此即時編譯器不敢隨便引入大規模的優化技術
-
2.java語言是動態的類型安全語言,這就意味著虛擬機必須頻繁地進行安全檢查
-
3.java語言中虛方法的使用頻率遠遠大於C/C++語言,
-
導致即時編譯器在進行一些優化時的難度要遠大於C/C++的靜態優化編譯器
-
4.java語言時可以動態擴展的語言,運行時加載新的類可能改變程序類型的繼承關系,
-
導致許多全局的優化措施都只能以激進優化的方式來完成
-
5.java虛擬機中對象的內存分配都是在堆上進行的
-
C++:靜態編譯器
-
1.編譯的時間成本在靜態優化編譯器中並不是主要的關註點
-
2.C/C++的對象則有多種分配方式,而且C/C++中主要由用戶程序代碼來回收分配的內存,
-
因此運行效率上比垃圾收集機制要高
-
java語言相對C/C++的劣勢都是為了換取開發效率上的優勢而付出的代價,
-
而且還有許多優化是java的即時編譯器能做而C/C++的靜態優化編譯器不能做或者不好做的,
-
如別名分析、調用頻率預測、分支頻率預測、裁剪為被選擇的分支等
-
執行引擎(三):程序編譯與代碼優化