深入理解JVM之編譯最佳化
JDK在原始碼編譯階段將原始碼編譯為JVM位元組碼,JVM位元組碼是一種平臺無關的中間程式碼方式,要由JVM在執行期間對其進行解釋並執行,這種方式成為位元組碼解釋執行方式。
對於物件導向的語言而言,最重要的是執行方法的指令,JVM有一套自己的執行方法的指令:invokestatic(呼叫static方法)、invokevirtual(呼叫物件例項的方法)、invokeinterface(呼叫介面的方法)、invokespecial(呼叫private方法和編譯原始碼後生成的方法,此方法為物件例項化時的初始化方法)
位元組碼是在棧中執行
執行緒建立時,會產生程式計數器(PC)、棧,PC存放下一條要執行的指令在方法內的偏移量。棧中存放棧幀,棧幀主要分為區域性變數區、運算元棧兩部分。
區域性變數區用於存放方法的區域性變數和引數,運算元棧用於存放方法執行過程中產生的中間結果,棧幀中還有一些其它空間,如方法已解析的常量池引用。
編譯後位元組碼:
code:
0:iconst_1 //將型別為int、值為1的常量放入運算元棧
1: istore_0 //將運算元棧中棧頂的值彈出放入區域性變數區
2:iconst_2 //將型別為int、值為2的常量放入運算元棧
3:istore_1 //將運算元棧中棧頂的值彈出放入區域性變數區
4:iload_0 //裝載區域性變數區中第一個值到運算元棧
5:iload_1 //裝載區域性變數區中第二個值到運算元棧
6:iadd //執行int型別的add指令,並將計算結果放入運算元棧
7:iconst_5 //將型別為int、值為5的常量放入運算元棧
8:imul //執行int型別的mul指令,並將計算結果放入運算元棧
9:istore_2 //將運算元棧中棧頂的值彈出並放入區域性變數區
10:return //返回
編譯執行
解釋執行的效率較低,為提升程式碼的執行效能,JVM提供將位元組碼編譯為機器碼的支援,編譯在執行時進行,通常稱為JIT編譯器,JVM在執行過程中對執行頻率高的程式碼進行編譯,對執行不頻繁的程式碼則繼續採用解釋執行的方式。
編譯執行有兩種模式:client compiler(-client)和server compiler(-server)
client compiler又稱為C1,較輕量級,只做少量效能開銷比較高的最佳化,它佔用記憶體少,適合桌面互動式應用,它的最佳化方式主要有:方法內聯,去虛擬化,冗餘削除等。
1,方法內聯:在方法中需要呼叫其它方法,需要經歷引數傳遞、返回值傳遞及跳轉等,方法內聯即把呼叫到的方法的指令直接植入到當前方法中
2,去虛擬化:在裝載class之後,進行類層次的分析,如發現介面的方法只提供一個實現類,那麼對於呼叫了此方法的程式碼,也可以進行方法內聯。
3,冗餘削除:在編譯時,根據執行時狀況進行程式碼摺疊或削除。去掉不需要的程式碼指令。
Server compiler又稱為C2,較為重量級,C2採用大量傳統編譯最佳化技巧,佔用記憶體多,適用於伺服器端應用。
“逃逸分析”是C2進行很多最佳化的基礎,逃逸分析是指根據執行狀況來判斷方法中的變數是否會被外部讀取,如不會則認為此變數是逃逸的,基於逃逸分析C2在編譯時會做標量替換、棧上分配、同步削除等
1,標量替換:用標量替換聚合量,見程式碼:
Point point = new Point(1,2);
System.out.println("point.x="+point.x+"; point.y="+point.y);
當point物件在後面的執行過程中未用到時,經過編譯後,程式碼會變成類似下面的結構:
int x = 1;
int y = 2;
System.out.println("point.x="+x+"; point.y="+y);
這種方式的好處是,如果建立的物件並未用到其中的全部變數,則可節省一定的記憶體,對於程式碼執行而言,由於無需去找物件的引用,也會更快一些。
2,棧上分配:如果上例中,point是逃逸的,那麼C2會選擇在棧上直接建立point物件例項,而不是在JVM堆上,在棧上分配的好處一方面是快速,另方面是垃圾回收時隨著方法的結束,物件也就被回收了。
3,同步削除:指同步的物件逃逸,方法外部沒有引用到同步的物件,那就沒有同步的必要了,C2編譯時會直接去掉同步。
JVM會根據機器配置來選擇C1還是C2,當機器配置CPU達到2核且記憶體超過2G則選擇C2,但是32位windows機器上始終選擇C1模式,也可在啟動時透過-client或-server來強制指定。
基於這個特性,在對java程式碼進行效能測試時,要注意是否實現做了足夠次數的呼叫,以保證測試是公平的。對於高效能的程式而言,也應考慮在程式提供給使用者訪問前,自行進行一定的呼叫,以保證關鍵功能的效能。
對於物件導向的語言而言,最重要的是執行方法的指令,JVM有一套自己的執行方法的指令:invokestatic(呼叫static方法)、invokevirtual(呼叫物件例項的方法)、invokeinterface(呼叫介面的方法)、invokespecial(呼叫private方法和編譯原始碼後生成的
位元組碼是在棧中執行
執行緒建立時,會產生程式計數器(PC)、棧,PC存放下一條要執行的指令在方法內的偏移量。棧中存放棧幀,棧幀主要分為區域性變數區、運算元棧兩部分。
區域性變數區用於存放方法的區域性變數和引數,運算元棧用於存放方法執行過程中產生的中間結果,棧幀中還有一些其它空間,如方法已解析的常量池引用。
點選(此處)摺疊或開啟
-
void foo(){
-
int a = 1;
-
int b = 2;
-
int c = (a + b) * 5;
- }
code:
0:iconst_1 //將型別為int、值為1的常量放入運算元棧
1: istore_0 //將運算元棧中棧頂的值彈出放入區域性變數區
2:iconst_2 //將型別為int、值為2的常量放入運算元棧
3:istore_1 //將運算元棧中棧頂的值彈出放入區域性變數區
4:iload_0 //裝載區域性變數區中第一個值到運算元棧
5:iload_1 //裝載區域性變數區中第二個值到運算元棧
6:iadd //執行int型別的add指令,並將計算結果放入運算元棧
7:iconst_5 //將型別為int、值為5的常量放入運算元棧
8:imul //執行int型別的mul指令,並將計算結果放入運算元棧
9:istore_2 //將運算元棧中棧頂的值彈出並放入區域性變數區
10:return //返回
編譯執行
解釋執行的效率較低,為提升程式碼的執行效能,JVM提供將位元組碼編譯為機器碼的支援,編譯在執行時進行,通常稱為JIT編譯器,JVM在執行過程中對執行頻率高的程式碼進行編譯,對執行不頻繁的程式碼則繼續採用解釋執行的方式。
編譯執行有兩種模式:client compiler(-client)和server compiler(-server)
client compiler又稱為C1,較輕量級,只做少量效能開銷比較高的最佳化,它佔用記憶體少,適合桌面互動式應用,它的最佳化方式主要有:方法內聯,去虛擬化,冗餘削除等。
1,方法內聯:在方法中需要呼叫其它方法,需要經歷引數傳遞、返回值傳遞及跳轉等,方法內聯即把呼叫到的方法的指令直接植入到當前方法中
2,去虛擬化:在裝載class之後,進行類層次的分析,如發現介面的方法只提供一個實現類,那麼對於呼叫了此方法的程式碼,也可以進行方法內聯。
3,冗餘削除:在編譯時,根據執行時狀況進行程式碼摺疊或削除。去掉不需要的程式碼指令。
Server compiler又稱為C2,較為重量級,C2採用大量傳統編譯最佳化技巧,佔用記憶體多,適用於伺服器端應用。
“逃逸分析”是C2進行很多最佳化的基礎,逃逸分析是指根據執行狀況來判斷方法中的變數是否會被外部讀取,如不會則認為此變數是逃逸的,基於逃逸分析C2在編譯時會做標量替換、棧上分配、同步削除等
1,標量替換:用標量替換聚合量,見程式碼:
Point point = new Point(1,2);
System.out.println("point.x="+point.x+"; point.y="+point.y);
當point物件在後面的執行過程中未用到時,經過編譯後,程式碼會變成類似下面的結構:
int x = 1;
int y = 2;
System.out.println("point.x="+x+"; point.y="+y);
這種方式的好處是,如果建立的物件並未用到其中的全部變數,則可節省一定的記憶體,對於程式碼執行而言,由於無需去找物件的引用,也會更快一些。
2,棧上分配:如果上例中,point是逃逸的,那麼C2會選擇在棧上直接建立point物件例項,而不是在JVM堆上,在棧上分配的好處一方面是快速,另方面是垃圾回收時隨著方法的結束,物件也就被回收了。
3,同步削除:指同步的物件逃逸,方法外部沒有引用到同步的物件,那就沒有同步的必要了,C2編譯時會直接去掉同步。
JVM會根據機器配置來選擇C1還是C2,當機器配置CPU達到2核且記憶體超過2G則選擇C2,但是32位windows機器上始終選擇C1模式,也可在啟動時透過-client或-server來強制指定。
基於這個特性,在對java程式碼進行效能測試時,要注意是否實現做了足夠次數的呼叫,以保證測試是公平的。對於高效能的程式而言,也應考慮在程式提供給使用者訪問前,自行進行一定的呼叫,以保證關鍵功能的效能。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28912557/viewspace-1478062/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- [譯]深入理解JVM Understanding JVM InternalsJVM
- 深入理解JVM之OOMJVMOOM
- 深入理解JVM讀書筆記四: (早期)編譯器優化JVM筆記編譯優化
- 深入理解JVM之類載入JVM
- 深入理解 JVM 之 JVM 記憶體結構JVM記憶體
- 深入理解JVM(1)之--JVM記憶體模型JVM記憶體模型
- iOS學習之深入理解程式編譯過程iOS編譯
- 要點提煉| 理解JVM之程式編譯&程式碼優化JVM編譯優化
- 深入理解 JVMJVM
- 深入理解JVMJVM
- 深入理解 JVM 之 垃圾收集器JVM
- 深入理解 JVM 之 垃圾回收機制JVM
- 深入理解JVM之記憶體管理JVM記憶體
- 深入理解Java虛擬機器之自己編譯JDKJava虛擬機編譯JDK
- 深入瞭解JVM虛擬機器8:Java的編譯期最佳化與執行期最佳化JVM虛擬機Java編譯
- 深入理解Java的動態編譯Java編譯
- 深入理解JVM——物件JVM物件
- 深入理解Hystrix之文件翻譯
- 深入理解flutter的編譯原理與優化Flutter編譯原理優化
- 深入理解JVM(一)——JVM記憶體模型JVM記憶體模型
- 深入理解JVM(一)JVM記憶體模型JVM記憶體模型
- JVM編譯優化JVM編譯優化
- 深入理解JVM虛擬機器6:深入理解JVM類載入機制JVM虛擬機
- 深入淺出JVM(七)之執行引擎的解釋執行與編譯執行JVM編譯
- 深入理解JVM效能調優JVM
- 深入理解JVM(3)—垃圾回收JVM
- 【轉】深入理解JVM--JVM垃圾回收機制JVM
- 深入理解JVM(③)——之HotSpot虛擬機器物件探祕JVMHotSpot虛擬機物件
- 深入理解Java虛擬機器-程式編譯與程式碼最佳化,華為Java影片面試Java虛擬機編譯面試
- 深入瞭解Java JIT編譯器:原理與效能最佳化Java編譯
- 深入理解泛型-重寫泛型類方法遇到的問題(涉及JVM反編譯位元組碼)泛型JVM編譯
- 深入理解JVM(③)Java的模組化JVMJava
- 《深入理解JVM》10-垃圾回收JVM
- 深入理解JVM(八)——java堆分析JVMJava
- 深入理解JVM(三)——配置引數JVM
- 深入理解JVM(五)——垃圾回收器JVM
- 深入理解JVM中的ClassLoaderJVM
- 深入理解JVM類載入器JVM