JVM(一)Java是編譯型語(yǔ)言還是解釋型語(yǔ)言?

1.JVM架構(gòu)

簡(jiǎn)介

java平臺(tái)可分為兩部分,既java虛擬機(jī)(JVM) 和JavaAPI類(lèi)庫(kù)。
JVM是Java Virtual Machine(java虛擬機(jī))的縮寫(xiě),JVM使得Java實(shí)現(xiàn)了跨平臺(tái)。

引入了JVM后,JAVA語(yǔ)言再不同平臺(tái)上運(yùn)行就不需要重新編譯(生成class文件,字節(jié)碼編譯部分),Java語(yǔ)言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的字節(jié)碼,就可以在多平臺(tái)不加修改地運(yùn)行。

JVM架構(gòu)圖

Java虛擬機(jī)主要分為5大模塊,類(lèi)裝載器子系統(tǒng),運(yùn)行時(shí)數(shù)據(jù)區(qū),執(zhí)行引擎,本地方法接口,垃圾收集模塊。


JVM架構(gòu)圖

JVM結(jié)構(gòu)

各部分主要功能:

類(lèi)加載器:JVM啟動(dòng),程序開(kāi)始運(yùn)行,負(fù)責(zé)將class字節(jié)碼加載到JVM內(nèi)存區(qū)域中。
執(zhí)行引擎:負(fù)責(zé)執(zhí)行文件中包含的字節(jié)碼指令(解釋執(zhí)行,即時(shí)編譯,OSR)
運(yùn)行時(shí)數(shù)據(jù)區(qū)

  • 方法區(qū)(元空間):用于存儲(chǔ)類(lèi)結(jié)構(gòu)信息的地方,包括常量池、靜態(tài)變量、構(gòu)造函數(shù)等
  • Java堆(Heap):存儲(chǔ)java實(shí)例的地方。這塊是GC的主要區(qū)域。方法區(qū)和堆是被線(xiàn)程共享的。
  • Java棧(Stack):java??偸呛途€(xiàn)程關(guān)聯(lián)在一起,每當(dāng)創(chuàng)建一個(gè)線(xiàn)程時(shí),JVM就會(huì)為這個(gè)線(xiàn)程創(chuàng)建一個(gè)對(duì)應(yīng)的java棧。每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧貞,用于存儲(chǔ)局部變量表、操作棧,方法返回等。
  • 程序計(jì)數(shù)器(PC Register):一塊較小的內(nèi)存空間,可以看做是當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼行號(hào)指示器,分支,循環(huán),跳轉(zhuǎn),異常處理,線(xiàn)程恢復(fù)等基礎(chǔ)功能都需要一來(lái)這個(gè)計(jì)數(shù)器來(lái)完成。
  • 本地方法棧(Native Method Stack):和java棧作用差不多,只不過(guò)是為JVM使用到的本地方法服務(wù)。
    本地方法接口:主要提供調(diào)用C或C++實(shí)現(xiàn)的本地方法。
    垃圾回收模塊:主要負(fù)責(zé)方法區(qū)和堆的垃圾回收。

JVM內(nèi)部模塊間的關(guān)系

模塊關(guān)系

垃圾回收系統(tǒng):方法區(qū),Java堆。
類(lèi)加載器:方法區(qū),Java堆。
執(zhí)行引擎:方法區(qū),Java堆,Java棧,程序計(jì)數(shù)器


2.編譯過(guò)程

java語(yǔ)言最顯著的兩個(gè)特點(diǎn):

  • 1.一次編譯,到處運(yùn)行:通過(guò)將java程序編譯成標(biāo)準(zhǔn)字節(jié)碼,而后通過(guò)JVM轉(zhuǎn)為對(duì)應(yīng)平臺(tái)的機(jī)器碼來(lái)屏蔽底層差異。
  • 2.自動(dòng)垃圾收集功能:通過(guò)java垃圾收集(Garbage Collector)回收分配內(nèi)存使得開(kāi)發(fā)人員不需要操心內(nèi)存的分配和回收。
差異屏蔽

先編譯后解釋

1.Java的編譯和執(zhí)行

以O(shè)racle 提供的HotSpot虛擬機(jī)為例,在HotSpot虛擬機(jī)中,提供了兩種編譯模式

  • 1.解釋執(zhí)行:逐條翻譯字節(jié)碼為可運(yùn)行的機(jī)器碼
  • 2.即時(shí)編譯:以方法為單位將字節(jié)碼翻譯成機(jī)器碼


    解釋器編譯器配合工作

編譯包括兩種情況

  • 1.源碼編譯成字節(jié)碼
  • 2.字節(jié)碼編譯成本地機(jī)器碼

解釋執(zhí)行也包括兩種情況

  • 1.源碼解釋執(zhí)行
  • 2.字節(jié)碼解釋執(zhí)行

我們先要清楚解釋執(zhí)行和編譯執(zhí)行的概念

解釋執(zhí)行:一邊對(duì)程序翻譯成機(jī)器碼,一邊交給計(jì)算機(jī)執(zhí)行。翻譯一句執(zhí)行一句。執(zhí)行速度慢,效率低,跨平臺(tái)性好。
編譯執(zhí)行: 對(duì)整個(gè)程序先翻譯成機(jī)器碼,然后計(jì)算機(jī)可以直接執(zhí)行。全部翻譯完再執(zhí)行。執(zhí)行速度快,效率高,跨平臺(tái)性差。

2.編譯原理

在執(zhí)行前先對(duì)程序源碼進(jìn)行詞法解析和語(yǔ)法解析處理,把源碼轉(zhuǎn)化為抽象語(yǔ)法樹(shù)


解釋執(zhí)行和編譯執(zhí)行前操作

其中綠色的模塊可以選擇性實(shí)現(xiàn)。
上圖中間那條分支是解釋執(zhí)行過(guò)程,而下面的那條分支就是傳統(tǒng)編譯原理中從源代碼到目標(biāo)機(jī)器代碼的生成過(guò)程。

3.三種編譯器

JVM的編譯器可以分為三個(gè)類(lèi)型

  • 前端編譯器:把java轉(zhuǎn)變?yōu)?class文件的過(guò)程。如Sun的Javac Eclipse JDT中的增量式編譯器。
  • 后端編譯器:它在程序運(yùn)行期間將字節(jié)碼轉(zhuǎn)變?yōu)闄C(jī)器碼。如(JIT Compiler)
  • AOT編譯器:靜態(tài)提前編譯器(Ahead Of Time Compiler),直接把.java文件部編譯成本地機(jī)器代碼,如JDK9中 實(shí)驗(yàn)性的IAOT。

4.編譯器-Javac編譯過(guò)程

.java文件是由Java源碼編譯器(javac)來(lái)完成,流程如圖:


javac編譯過(guò)程

默認(rèn)情況下,無(wú)論是方法即時(shí)編譯還是OSR,編譯未完成之前,都扔按照解釋方式執(zhí)行。而編譯動(dòng)作在后臺(tái)編譯線(xiàn)程中進(jìn)行,用戶(hù)通過(guò)-XX:-BackgroundCompilation來(lái)禁止后臺(tái)編譯,這樣執(zhí)行線(xiàn)程提交了編譯請(qǐng)求后會(huì)等待。編譯完成后繼續(xù)執(zhí)行。

5運(yùn)行期-JIT編譯

目前主流的JVM都是混合模式(mixed)即解釋運(yùn)行和編譯運(yùn)行配合使用。流程如下

  • 1.java代碼經(jīng)過(guò)javac編譯成class文件(字節(jié)碼)
  • 2.class文件(字節(jié)碼)經(jīng)過(guò)jvm變異成機(jī)器碼進(jìn)行解釋執(zhí)行
  • 3.對(duì)于熱點(diǎn)代碼,JIT(JustInTime)編譯器會(huì)在運(yùn)行時(shí)將其編譯為機(jī)器碼執(zhí)行。

HotSpot JVm中有兩個(gè)JIT compiler,一個(gè)是C1,對(duì)應(yīng)的模式是client,一個(gè)是C2,對(duì)應(yīng)模式是server。

C1:即Client編譯器,面向?qū)?dòng)性有要求的客戶(hù)端GUI程序,采用的優(yōu)化手段比較簡(jiǎn)單,因此編譯的時(shí)間短。
C2: 即Server編譯器,面向?qū)π阅芊逯涤幸蟮姆?wù)端程序,采用的優(yōu)化手段較復(fù)雜,因此編譯時(shí)間長(zhǎng),但是在運(yùn)行過(guò)程中性能更好。

OSR編譯(On Stack Replace):只替換循環(huán)代碼體的入口,C1、C2替換的是方法調(diào)用的入口。因此OSR編譯后會(huì)出現(xiàn)的現(xiàn)象是方法的整段代碼被編譯了,但是只有循環(huán)體部分才執(zhí)行編譯后的機(jī)器碼,其他部分仍是解釋執(zhí)行。

可以根據(jù)場(chǎng)景在虛擬機(jī)啟動(dòng)的時(shí)候定制不同的運(yùn)行模式

  • 1.mixed模式:用-Xmixed開(kāi)啟,即混合模式,也是HotSpot默認(rèn)模式
  • 2.int模式:用-Xint開(kāi)啟,即解釋模式,在這種模式下全部才去解釋模式運(yùn)行
  • 3.comp模式:用-Xcomp開(kāi)啟,這種模式下通知jvm關(guān)閉解釋模式,采用編譯模式來(lái)運(yùn)行,但往往導(dǎo)致無(wú)法得到良好的自動(dòng)優(yōu)化。

在JDK9中 提供了AOT(Ahead-of-Time Compilation) 編譯器允許將代碼編譯成機(jī)器碼交給JVM運(yùn)行。
在JDK10中提供了Graal即時(shí)編譯器(實(shí)驗(yàn)性的)。

從Java7開(kāi)始,HotSpot虛擬機(jī)默認(rèn)多采用分層編譯的方式:

1.熱點(diǎn)方法首先被C1編譯器編譯。
2.熱點(diǎn)方法中的熱點(diǎn)方法再進(jìn)一步被C2編譯器編譯。
3.為了不干擾程序正常運(yùn)行,JIT編譯時(shí)在額外的線(xiàn)程中執(zhí)行。HotSpot根據(jù)實(shí)際CPU資源,以1:2的比例分配給C1和C2線(xiàn)程數(shù)。在計(jì)算機(jī)資源充足的情況,字節(jié)碼的解釋運(yùn)行和編譯運(yùn)行可以同時(shí)進(jìn)行。編譯執(zhí)行完后的機(jī)器碼會(huì)在下次調(diào)用該方法時(shí)啟動(dòng),以替換原本的解釋執(zhí)行。

C1 編譯器編譯過(guò)程
它是一個(gè)簡(jiǎn)單快速的三段式編譯器,主要的關(guān)注點(diǎn)在于局部性的優(yōu)化,而放棄了許多耗時(shí)較長(zhǎng)的全局化手段。

  • 1.在第一個(gè)階段,一個(gè)平臺(tái)獨(dú)立的前端將字節(jié)碼構(gòu)造成一種高級(jí)中間代碼(High-Level Intermediate Representaion,HIR)。HIR使用靜態(tài)單分配(Static single Assignment,SSA)的形式來(lái)代碼代碼值,這可以使得一些在HIR的構(gòu)造過(guò)程之中和之后進(jìn)行優(yōu)化動(dòng)作更容易實(shí)現(xiàn)。在此之前編譯器會(huì)在字節(jié)碼上完成一部分基礎(chǔ)優(yōu)化,如:方法內(nèi)聯(lián)、常量傳播等優(yōu)化將會(huì)在字節(jié)碼被構(gòu)造成HIR之前完成
  • 2.在第二階段,一個(gè)平臺(tái)相關(guān)的后端從HIR中產(chǎn)生低級(jí)中間代碼表示(Low_level Intermediate Representation,LIR),而在此之前會(huì)在HIR上完成另外一些優(yōu)化,如空值檢查消除、范圍檢查消除等,以便讓HIR達(dá)到更高效的代碼表示形式。
  • 3.最后階段是在平臺(tái)相關(guān)的后端使用線(xiàn)性?huà)呙杷惴ǎ↙ineal Scan Register Allocation)在LIR上做窺孔(Peephole)優(yōu)化,然后產(chǎn)生機(jī)器代碼。
流程圖

C2編譯器編譯過(guò)程
Server Compoler是專(zhuān)門(mén)面向服務(wù)端的典型應(yīng)用并為服務(wù)端的性能配置特別調(diào)整過(guò)的編譯器,也是一個(gè)充分優(yōu)化過(guò)的高級(jí)編譯器。

  • 1.會(huì)執(zhí)行所有經(jīng)典的優(yōu)化動(dòng)作,如:無(wú)用代碼消除,循環(huán)展開(kāi),循環(huán)表達(dá)式外提,消除公共子表達(dá)式,常量傳播,基本快重排序等。
  • 2.還會(huì)實(shí)施一項(xiàng)與java語(yǔ)言特性密切相關(guān)的優(yōu)化技術(shù),如范圍消除,控制檢查消除。
  • 3.Server Compiler的寄存器分配器是全局圖著色分配器,從即時(shí)編譯器的角度來(lái)看無(wú)疑是比較緩慢的。但是編譯速度仍然遠(yuǎn)遠(yuǎn)超過(guò)傳統(tǒng)靜態(tài)優(yōu)化編譯器,而相對(duì)于Client Compiler編譯,輸出的代碼質(zhì)量有所提高,可減少本地代碼的執(zhí)行時(shí)間。

在運(yùn)行過(guò)程中會(huì)被即時(shí)編譯的熱點(diǎn)代碼有兩類(lèi):

  • 1.被多次調(diào)用的方法體:編譯器會(huì)將整個(gè)方法作為編譯對(duì)象,這也是標(biāo)準(zhǔn)JIT編譯方式。
  • 2.被多次執(zhí)行的循環(huán)體:是由循環(huán)體發(fā)出的,但是編譯器依然以整個(gè)方作為編譯對(duì)象,因?yàn)榘l(fā)生在方法執(zhí)行過(guò)程中,被稱(chēng)為棧上替換。

判斷一段代碼是否是熱點(diǎn)代碼,是不是需要觸發(fā)即時(shí)編譯,這樣的行為稱(chēng)為熱點(diǎn)探測(cè)(Hot Spot Detection
)探測(cè)方法有兩種:

1.基于采樣的熱點(diǎn)探測(cè)(Sample Based Hot Spot Detection):虛擬機(jī)周期的堆各個(gè)系成功的棧頂進(jìn)行檢查,如果某些方法經(jīng)常出現(xiàn)在棧頂,這個(gè)方法就是熱點(diǎn)方法。好處是簡(jiǎn)單高效,缺點(diǎn)是很難確認(rèn)方法是不是熱點(diǎn),容易受到線(xiàn)程阻塞或其他外因的干擾。
2.基于計(jì)數(shù)器的熱點(diǎn)探測(cè)(Counter Based Hot Spot Detection):為每個(gè)方法建立計(jì)數(shù)器,執(zhí)行次數(shù)超過(guò)閾值就認(rèn)為是熱點(diǎn)方法。優(yōu)點(diǎn)是精確嚴(yán)謹(jǐn),缺點(diǎn)是實(shí)現(xiàn)麻煩,不能直接獲取方法調(diào)用關(guān)系。

HotSpot使用的是第二種,基于計(jì)數(shù)器的熱點(diǎn)探測(cè)。并且有兩類(lèi)計(jì)數(shù)器:

1.方法調(diào)用計(jì)數(shù)器(Invocation Counter):Client模式下默認(rèn)閾值是1500次,在Server模式下是10000次,這個(gè)閾值可以通過(guò)-XX:CompileThreadhold來(lái)人為設(shè)定。如果不做任何設(shè)置,方法調(diào)用計(jì)數(shù)器并不是方法被調(diào)用的絕對(duì)次數(shù)。當(dāng)超過(guò)一定時(shí)間限度,方法的調(diào)用次數(shù)仍然不足以讓它交給即時(shí)編譯器編譯,那么這個(gè)方法的調(diào)用技術(shù)就會(huì)被減少一半(熱度衰減),這段時(shí)間就成為此方法統(tǒng)計(jì)的半衰周期。進(jìn)行熱度衰減的動(dòng)作是在垃圾回收時(shí)順便進(jìn)行的,可以使用虛擬機(jī)參數(shù)-XX:CounterHalfLifeTime參數(shù)設(shè)置半衰周期 單位是秒。
2.回邊調(diào)用計(jì)數(shù)器(Back Edge Counter):作用是統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼的執(zhí)行次數(shù)。在字節(jié)碼中遇到控制流向跳轉(zhuǎn)的指令稱(chēng)為“回邊”(Back Edge)。默認(rèn)設(shè)置下虛擬機(jī)回邊計(jì)數(shù)器閾值為 10700。


6.編譯優(yōu)化

6.1語(yǔ)法糖(早期Javac階段)

語(yǔ)法糖可以看做是編譯器實(shí)現(xiàn)的一些小把戲,這些小把戲可能會(huì)使效率大提升。
Java中最常用的語(yǔ)法糖主要有泛型、變長(zhǎng)參數(shù)、條件編譯、自動(dòng)拆裝箱,內(nèi)部類(lèi),遍歷循環(huán),枚舉類(lèi),斷言語(yǔ)句,字符串的switch,try等。虛擬機(jī)并不支持這些語(yǔ)法,他們?cè)诰幾g階段就被還原回了簡(jiǎn)單的基礎(chǔ)語(yǔ)法結(jié)構(gòu),這個(gè)過(guò)程稱(chēng)為解語(yǔ)法糖。

6.1.1 泛型與類(lèi)擦除

Java語(yǔ)言在JDK1.5之后引入的泛型實(shí)際上只在程序源碼中存在,在編譯后的字節(jié)碼文件中,泛型會(huì)被替換為原來(lái)的原生類(lèi)型(Raw Type,也稱(chēng)為裸類(lèi)型)。這個(gè)過(guò)程被稱(chēng)為泛型擦除。并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō),ArrayList<String>和ArrayList<Integer>就是同一個(gè)類(lèi)。所以泛型技術(shù)實(shí)際上是java語(yǔ)言的一棵語(yǔ)法糖,基于這種方法實(shí)現(xiàn)的泛型被稱(chēng)為偽泛型。

Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"No.1");
map.put(2,"No.2");
System.out.println(map.get(1));
System.out.println(map.get(2));
    
//將這段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都變回了原生類(lèi)型,如下面的代碼所示:
Map map = new HashMap();
map.put(1,"No.1");
map.put(2,"No.2");
System.out.println((String)map.get(1));
System.out.println((String)map.get(2));
    
//為了更詳細(xì)地說(shuō)明類(lèi)型擦除,再看如下代碼:
import java.util.List;
public class FanxingTest{
    public void method(List<String> list){
        System.out.println("List String");
    }
    public void method(List<Integer> list){
        System.out.println("List Int");
    }
}

//用Javac編譯器編譯這段代碼時(shí),報(bào)出了如下錯(cuò)誤:
FanxingTest.java:3: 名稱(chēng)沖突:method(java.util.List<java.lang.String>) 和 method

(java.util.List<java.lang.Integer>) 具有相同疑符
        public void method(List<String> list){
                    ^
FanxingTest.java:6: 名稱(chēng)沖突:method(java.util.List<java.lang.Integer>) 和 method
d(java.util.List<java.lang.String>) 具有相同疑符
        public void method(List<Integer> list){
                    ^

//這是因?yàn)榉盒蚅ist<String>和List<Integer>編譯后都被擦除了,變成了一樣的原生類(lèi)型List

泛型的功能使用Object完全能夠替代。
有了泛型這顆語(yǔ)法糖后:

  • 1.代碼更加簡(jiǎn)潔 【不需要強(qiáng)制轉(zhuǎn)換】。
  • 2.程序更加健壯 【只要編譯器沒(méi)有警告,那么運(yùn)行時(shí)期就不會(huì)出現(xiàn)ClassCastException】
  • 3.可讀性和穩(wěn)定性【在編寫(xiě)的時(shí)候就限定了類(lèi)型】
6.1.2條件編譯

java編譯器并非一個(gè)個(gè)的編譯Java文件,而是將所有編譯單元的語(yǔ)法樹(shù)頂級(jí)節(jié)點(diǎn)輸入到待處理列表后再進(jìn)行編譯,因此各個(gè)文件之間能夠互相提供符號(hào)信息。
java語(yǔ)言使用條件為常量的if語(yǔ)句時(shí),它在編譯階段就會(huì)被運(yùn)行,生成的字節(jié)碼之中只包含條件正確的部分。編譯器會(huì)把分支不成立的代碼擦除掉,這一工作在編譯器解除語(yǔ)法糖階段完成。

6.1.3自動(dòng)拆箱裝箱與遍歷循環(huán)

自動(dòng)裝箱、拆箱是在編譯之后就被轉(zhuǎn)換成了想用的包裝個(gè)還原方法,如Integer.valueOf()與Integer.intValue()方法。
遍歷循環(huán)則把代碼還原成了迭代器的實(shí)現(xiàn),這也是為何遍歷循環(huán)需要被遍歷類(lèi)實(shí)現(xiàn)Iterable接口的原因。
包裝類(lèi)的“==”運(yùn)算在不遇到算數(shù)運(yùn)算的情況下不會(huì)自動(dòng)拆箱,它們的equals()方法不處理數(shù)據(jù)轉(zhuǎn)型的關(guān)系。

6.2即時(shí)編譯器JIT 優(yōu)化技術(shù)

首先需要明確的是,這些代碼優(yōu)化變換是建立在代碼的某種中間表示或機(jī)器碼之上,絕不是建立在Java源碼之上的。


優(yōu)化技術(shù)

優(yōu)化技術(shù)

對(duì)代碼的所有優(yōu)化措施都集中在即時(shí)編譯器中(JIT)。

常用優(yōu)化技術(shù)

  • 1.方法內(nèi)聯(lián)(Method Inlining)
  • 2.冗余訪(fǎng)問(wèn)消除(Redundant Loads Elimination)
  • 3.復(fù)寫(xiě)傳播(Copy Propagation)
  • 4.無(wú)用代碼消除(Dead Code Elimination)
  • 5.公共子表達(dá)式消除
  • 6.數(shù)組邊界檢查消除(Array Bounds Checking Elimination)
  • 7.逃逸分析
1.方法內(nèi)聯(lián)

去除方法調(diào)用的成本(如建立棧幀等),并為其他優(yōu)化建立良好基礎(chǔ)。
方法內(nèi)聯(lián)就是把被調(diào)用函數(shù)代碼“復(fù)制”到調(diào)用方函數(shù)中,來(lái)減少函數(shù)調(diào)用開(kāi)銷(xiāo)。(棧幀)

函數(shù)調(diào)用的壓棧和出棧過(guò)程,需要有一定的時(shí)間開(kāi)銷(xiāo)和空間開(kāi)銷(xiāo)。
當(dāng)一個(gè)方法體不大,但又頻繁被調(diào)用時(shí),時(shí)間和空間開(kāi)銷(xiāo)會(huì)變得很大,非常不劃算,降低程序性能。
 private int add(int x,int y,int z,int k) {
    return add1(x,y) + add1(z,k);
}

private int add1(int x , int y) {
      return x + y;
}
內(nèi)聯(lián)后
 private int add(int x,int y,int z, int k) {
    return x + y + z +k;
}

內(nèi)聯(lián)觸發(fā)條件
JVM會(huì)自動(dòng)識(shí)別熱點(diǎn)方法并對(duì)它們使用方法內(nèi)聯(lián)優(yōu)化

  • 1.client編譯器 默認(rèn)為1500
  • 2.server編譯器 默認(rèn)為10000
    通常這個(gè)值由 -XX:CompileThreshold參數(shù)設(shè)置。
    但是一個(gè)方法就算被JVM標(biāo)記為熱點(diǎn)方法,JVM也不一定會(huì)對(duì)他最方法內(nèi)聯(lián)優(yōu)化。其中有個(gè)比較常見(jiàn)的原因就是方法體太大了。
  • 1.如果方法是經(jīng)常執(zhí)行的,默認(rèn)情況下,方法大小小于325字節(jié)的都會(huì)進(jìn)行內(nèi)聯(lián)(-XX:MaxFreqInineSize 參數(shù)設(shè)置)
  • 2.如果方法不是經(jīng)常執(zhí)行的,默認(rèn)情況下,方法大小小于35字節(jié)才會(huì)進(jìn)行內(nèi)聯(lián)(-XX:MaxInLineSize 參數(shù)設(shè)置)
2.冗余訪(fǎng)問(wèn)消除
static class B {
    int value;
    final int get() {
        return value;
    }
}

public void foo() {
    y = b.get();
    //do something
    z = b.get();
    sum = y + z;
}

如果代碼中 do something 所代碼的操作不會(huì)改變b.value的值,那就可以把`z = b.get()` 替換為`z = y`
因?yàn)樯弦痪鋊y = b.get()`已經(jīng)保證了變量y 與`b.get()`是一致的,這樣就可以不再去訪(fǎng)問(wèn)對(duì)象b的局部變量了。
3.復(fù)寫(xiě)傳播

在上面的程序的邏輯中并沒(méi)有必要使用一個(gè)額外的變量z,它與變量y是完全相等的,因此可以用y來(lái)代替z

4.無(wú)用代碼消除

無(wú)用代碼可能是永遠(yuǎn)不會(huì)被執(zhí)行的代碼,也可能是完全沒(méi)有意義的代碼,因此,它又形象地被成為Dead Code

5.公共子表達(dá)式消除

如果一個(gè)表達(dá)式E已經(jīng)計(jì)算過(guò)了,并且從先前到現(xiàn)在E中所有變量的值沒(méi)有發(fā)生變化,那E的這次出現(xiàn)就成為了公共子表達(dá)式。
對(duì)這種表達(dá)式?jīng)]必要再花時(shí)間進(jìn)行計(jì)算,只需直接用前面幾歲安國(guó)的表達(dá)式結(jié)構(gòu)結(jié)果代替即可。
如果這種優(yōu)化僅限于程序的基本塊內(nèi),便稱(chēng)為局部公共子表達(dá)式消除,如果覆蓋了多個(gè)基本塊則成為全局公共子表達(dá)式消除。

6.數(shù)組邊界檢查消除

7.逃逸分析
  • 1.分析對(duì)象動(dòng)態(tài)作用域,當(dāng)一個(gè)方法被定以后,它可能被外部方法所引用,稱(chēng)為方法逃逸,甚至還有可能被外部線(xiàn)程訪(fǎng)問(wèn)到,稱(chēng)為線(xiàn)程逃逸。
  • 2.若能證明一個(gè)對(duì)象不會(huì)逃逸到方法或線(xiàn)程之外,那么就可以通過(guò)棧上分配、同步消除、標(biāo)量替換來(lái)進(jìn)行優(yōu)化。
  • 3.棧上分配:在一般應(yīng)用中不會(huì)逃逸的局部對(duì)象所占比例很大,若能在棧上分配內(nèi)存隨著方法結(jié)束而銷(xiāo)毀,垃圾回收系統(tǒng)壓力將會(huì)小很多。
  • 4.標(biāo)量替換:標(biāo)量,指的是JVM中描述數(shù)據(jù)最基本的單位。例如原始數(shù)據(jù)類(lèi)型等。當(dāng)確定一個(gè)對(duì)象不會(huì)逃逸后,那么就要分配它到棧上空間,然后??臻g是有限的,為了進(jìn)一步節(jié)省??臻g,就需要將對(duì)象(聚合量)拆散為標(biāo)量。這樣在JVM里不會(huì)在棧中創(chuàng)建對(duì)象,而是僅僅創(chuàng)建對(duì)象的成員變量,這樣就節(jié)省了空間,而且沒(méi)有對(duì)象頭以及對(duì)齊填充的空間浪費(fèi)。
  • 5 同步消除:同樣基于逃逸分析,當(dāng)加鎖的變量不會(huì)發(fā)生逃逸,是線(xiàn)程私有的,那么完全沒(méi)有必要加鎖,JIT編譯時(shí)期就可以將同步鎖去掉,以減少加鎖解鎖造成的資源開(kāi)銷(xiāo)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容