小編給大家分享一下JAVA虛擬機(jī)JVM如何優(yōu)化,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
目前創(chuàng)新互聯(lián)已為近千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬空間、綿陽服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計(jì)、太康網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
還以這個(gè)圖為例,從.java到.class是編譯過程,從.class到機(jī)器碼是解釋過程。下面對(duì)其進(jìn)行分別優(yōu)化。在優(yōu)化過程中,對(duì)編譯階段的優(yōu)化主要是對(duì)前端編譯器的優(yōu)化,在運(yùn)行階段的優(yōu)化,主要是對(duì)即時(shí)編譯器的優(yōu)化。
編譯器優(yōu)化
編譯過程
以上為javac的編譯過程圖,以下為javac編譯過程的主體代碼。
下面對(duì)其步驟進(jìn)行詳細(xì)解讀
1、解析與填充符號(hào)表
詞法分析
將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記(Token)集合,標(biāo)記是編譯過程中的最小元素,如a,=,b,int。
語法分析
根據(jù)Token序列構(gòu)造抽象語法樹。以后編譯器基本不會(huì)再對(duì)源碼文件進(jìn)行操作了,后續(xù)的操作都是建立在抽象語法樹上。抽象語法樹是一種用來描述程序代碼語法結(jié)構(gòu)的樹形表示方式,節(jié)點(diǎn)代表代碼中的一個(gè)語法結(jié)構(gòu),例如修飾符,返回值等。
填充符號(hào)表
符號(hào)表是由一組符號(hào)地址和符號(hào)信息構(gòu)成的表格,用于編譯的不同階段。如在語義分析中,用于語義檢查和產(chǎn)生中間代碼;在目標(biāo)代碼生成階段,用于地址分配的依據(jù)。
2、注解處理器
這部分是插入式注解處理器在編譯期間對(duì)注解進(jìn)行處理的過程。其可以對(duì)語法樹進(jìn)行修改,一旦進(jìn)行了修改,編譯器將回到上面的第一步進(jìn)行重新處理,每一次循環(huán)稱為一個(gè)Round,也就是上圖中的回環(huán)過程。
3、語義分析與字節(jié)碼生成
在經(jīng)過語法分析后,生成的語法樹是一個(gè)結(jié)構(gòu)正確的源程序的抽象,但無法保證源程序是符合邏輯的。語義分析的任務(wù)是對(duì)結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查。比如下面代碼中的錯(cuò)誤只能在語義分析階段檢查出來。
boolean a=false; char b=2; int c=a+b
此階段包括如下4個(gè)步驟:
標(biāo)注檢查
變量使用前是否已被聲明、變量與賦值之間的數(shù)據(jù)類型是否能夠匹配等。還有一個(gè)常量折疊,即把a(bǔ)=1+2變?yōu)閍=3。所以在代碼中的a=1+2和a=3并不會(huì)增加程序運(yùn)行期cpu指令的運(yùn)算量。
數(shù)據(jù)及控制流分析
檢查程序局部變量在使用前是否有賦值,方法的每條路徑是否都有返回值,是否所有的受查異常都被正確處理了等問題。在類加載時(shí)也有一個(gè)數(shù)據(jù)及控制流分析,其目的基本是一致的,但校驗(yàn)的范圍不同,有些校驗(yàn)項(xiàng)只有在編譯期或運(yùn)行期才能運(yùn)行。
解語法糖
語法糖是在計(jì)算機(jī)語言中添加某種語法,其對(duì)語言的功能沒有影響,但是能提高程序的可讀性。語法糖包括泛型,自動(dòng)拆裝箱等。虛擬機(jī)運(yùn)行時(shí)不支持這些語法,它們?cè)诰幾g階段還原回基礎(chǔ)語法結(jié)構(gòu)。這個(gè)過程稱為解語法糖。
字節(jié)碼生成
將之前步驟生成的信息(語法樹、符號(hào)表)轉(zhuǎn)化成字節(jié)碼寫到磁盤中,然后添加和轉(zhuǎn)換了少量的代碼。如把字符串的加操作替換為StringBuffer或StringBuilder的append()操作。
至此,Class文件生成了。
語法糖
語法糖是java中添加某種語法,對(duì)語言的功能沒有影響,但是可以增加程序的可讀性。包括泛型、內(nèi)部類、枚舉類等。
1、泛型與類型擦除
泛型可用于類、接口和方法的創(chuàng)建中,用于對(duì)放入集合元素的類型的約束。泛型只在程序源碼中存在,在編譯階段有解語法糖的步驟,所以在.Class文件中,已經(jīng)變?yōu)榱嗽瓉淼脑愋土?。這個(gè)過程叫做類型擦除。
泛型擦除前:
public static void main(String[] args){ Mapmap=new HashMap<>(); map.put("姓名","小明"); map.put("性別","男"); sout(map.get("姓名")); sout(map.get("性別")); }
泛型擦除后:
public static void main(String[] args){ Map map=new HashMap(); map.put("姓名","小明"); map.put("性別","男"); sout((String)map.get("姓名")); sout((String)map.get("性別")); }
所以ArrayList和ArrayList在運(yùn)行期時(shí)是同一個(gè)類。
2、自動(dòng)拆裝箱、循環(huán)遍歷
這些是java中使用最多的語法糖。編譯前:
public static void main(String[] args){ Listlist=Arrays.asList(1,2,3,4); int sum=0; for(int i:list){ sum +=i; } System.out.println(sum); }
編譯后:
public static void main(String[] args){ List list=Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)}); int sum=0; for(Iterator localIterator=list.iterator();localIterator.hasNext();){ int i=((Integer)localIterator.next()).intValue(); sum +=i; } System.out.println(sum); }
可見,自動(dòng)拆裝箱在編譯后被轉(zhuǎn)化為了對(duì)應(yīng)的包裝和還原方法,如Integer.valueOf()和Integer.intValue()。
遍歷循環(huán)則把代碼還原為了迭代器的實(shí)現(xiàn)。
3、條件編譯
根據(jù)布爾常量值的真假,編譯器會(huì)把分支中不成立的代碼塊消除掉。
public static void main(String[] args){ if(true){ sout("block 1"); }else{ sout("block 2"); } }
編譯后,代碼變?yōu)椋?/p>
public static void main(String[] args){ sout("block 1"); }
運(yùn)行期優(yōu)化
一般情況下,我們將.java編譯成.class,.class再解釋成機(jī)器碼。但是也有特殊的情況。有些代碼調(diào)用比較頻繁,比如某個(gè)方法或代碼塊的運(yùn)行特別頻繁,為了提高程序的執(zhí)行效率,在運(yùn)行時(shí),虛擬機(jī)會(huì)把這個(gè)代碼直接編譯成機(jī)器碼,并進(jìn)行各種層次的優(yōu)化。這樣的代碼稱為熱點(diǎn)代碼。完成這個(gè)任務(wù)的編譯器被稱為即時(shí)編譯器。但是其并不是虛擬機(jī)必需的部分。
即時(shí)編譯器的概述
(1)為什么虛擬機(jī)要使用解釋器和編譯器并存的架構(gòu)?
虛擬機(jī)里包含著解釋器和編譯器。當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用,省去編譯的時(shí)間,立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率。
當(dāng)程序運(yùn)行環(huán)境中內(nèi)存資源限制較大,可以使用解釋執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯來提升效率。
(2)為什么虛擬機(jī)要實(shí)現(xiàn)兩個(gè)不同的即時(shí)編譯器?
虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器,分別為Client Compiler和Server Compiler,又稱為C1和C2。
默認(rèn)只使用其中的一個(gè),至于選擇哪個(gè),取決于虛擬機(jī)會(huì)根據(jù)自身版本和宿主機(jī)器的硬件性能自動(dòng)選擇運(yùn)行模式。用戶也可以使用“-client”、“-server”進(jìn)行指定。
(3)程序何時(shí)使用解釋器執(zhí)行?何時(shí)使用編譯器執(zhí)行?
虛擬機(jī)有一個(gè)分層編譯策略。
第0層:程序解釋執(zhí)行,解釋器不開啟性能監(jiān)控功能,可觸發(fā)第1層編譯
第1層:也稱為C1編譯,將字節(jié)碼編譯為本地代碼,進(jìn)行簡單、可靠的優(yōu)化,如有必要將加入性能監(jiān)控的邏輯。
第2層:也稱為C2編譯,將字節(jié)碼編譯為本地代碼,但是會(huì)啟用一些編譯耗時(shí)較長的優(yōu)化,甚至?xí)鶕?jù)性能監(jiān)控信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化。
(4)哪些程序代碼會(huì)被編譯為本地代碼?如何編譯為本地代碼?
熱點(diǎn)代碼包括如下兩類,其均把整個(gè)方法作為編譯對(duì)象。
a、被多次調(diào)用的方法
b、被多次執(zhí)行的循環(huán)體
熱點(diǎn)探測是用來判斷一段代碼是否為熱點(diǎn)代碼,其方式有兩種:
a、基于采樣
b、基于計(jì)數(shù)器。HotSpot使用的是這種。它為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:統(tǒng)計(jì)方法被調(diào)用次數(shù)的方法調(diào)用計(jì)數(shù)器和統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行次數(shù)的回邊計(jì)數(shù)器。
(5)如何從外部觀察及時(shí)編譯器的編譯過程和編譯結(jié)果?
可以使用 -xx:+PrintCompilation 查看哪些方法被即時(shí)編譯器編譯了。
優(yōu)化技術(shù)有哪些?
虛擬機(jī)的即時(shí)編譯器在生成代碼時(shí),采用了如下的代碼優(yōu)化技術(shù)。
(1)公共子表達(dá)式消除
如果一個(gè)表達(dá)式E已經(jīng)計(jì)算過了,那如果再次出現(xiàn)E時(shí)就不會(huì)再對(duì)它進(jìn)行計(jì)算。比如:
int d=(a*b)*12+c+(c+b*a)
如果這段代碼交給javac編譯器,則不會(huì)進(jìn)行任何優(yōu)化。如果交給即時(shí)編譯器,會(huì)被進(jìn)行如下步驟的優(yōu)化:
第一步:消除公共子表達(dá)式
int d=E*12+c+(c+E)
第二步:代數(shù)化簡:
int d=E*13+c*2
(2)數(shù)組邊界檢查消除
數(shù)組邊界檢查是什么?
如果有一個(gè)數(shù)組foo[],在java語言中訪問數(shù)組元素foo[i]的時(shí)候,系統(tǒng)將會(huì)自動(dòng)進(jìn)行上下界的范圍檢查,檢查i是否滿足0≤i≤foo.length這個(gè)條件。
那怎么進(jìn)行消除呢?
a、把運(yùn)行期檢查提到編譯期完成。如foo[3],只要在編譯期根據(jù)數(shù)據(jù)流分析來確定foo.length的值,并判斷下標(biāo)“3”沒有越界,執(zhí)行的時(shí)候就不用判斷了。
b、隱式異常處理。這種思路通常用于空指針檢查和算符運(yùn)算中除數(shù)為零的情況。
if(foo!=null){ return foo.value; }else{ throw new NullPointException(); }
被隱式異常處理優(yōu)化后,變?yōu)槿缦麓a:
try{ return foo.value; }catch(segment_fault){ uncommon_trap(); }
除了數(shù)組邊界檢查消除,還有自動(dòng)裝箱消除、安全點(diǎn)消除、消除反射等。
(3)方法內(nèi)聯(lián)
把目標(biāo)方法的代碼“復(fù)制”到發(fā)起調(diào)用的方法之中,避免發(fā)生真實(shí)的方法調(diào)用。
public int add(int x1, int x2, int x3, int x4) { return add1(x1, x2) + add1(x3, x4); } public int add1(int x1, int x2) { return x1 + x2; }
運(yùn)行一段時(shí)間后JVM會(huì)把a(bǔ)dd1方法去掉,并把代碼翻譯成:
public int add(int x1, int x2, int x3, int x4) { return x1 + x2 + x3 + x4; }
(4)逃逸分析
當(dāng)一個(gè)對(duì)象在方法中被定義后,它可能被外部方法所引用,比如作為調(diào)用參數(shù)傳遞到其它方法中,這稱為方法逃逸。同理,如果被外部線程訪問到,它就稱為線程逃逸。
對(duì)變量進(jìn)行相應(yīng)分析就叫做逃逸分析。如果能證明別的方法或線程無法通過任何途徑訪問到這個(gè)對(duì)象,則可以為這個(gè)變量進(jìn)行一些優(yōu)化。
優(yōu)化的手段有棧上分配、同步消除、標(biāo)量替換等。以同步消除為例,如果逃逸分析能夠確定一個(gè)變量不會(huì)逃逸出線程,即無法被其它線程訪問到,那對(duì)這個(gè)變量實(shí)施的同步措施就可以消除掉了。
以上是JAVA虛擬機(jī)JVM如何優(yōu)化的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!