JVM Client 模式和 Server模式的區(qū)別
通過 java -version 可查看 JVM 所處的模式,并可以通過修改配置文件進行配置,那它們有什么區(qū)別呢?
Server:-Server 模式啟動時,速度較慢,但是啟動之后,性能更高,適合運行服務(wù)器后臺程序
Client:-Client 模式啟動時,速度較快,啟動之后不如 Server,適合用于桌面等有界面的程序
熱點代碼
理解
當(dāng)虛擬機發(fā)現(xiàn)某個方法或代碼塊的運行特別頻繁時,就會把這些代碼認定為“熱點代碼”。
熱點代碼的分類
- 被多次調(diào)用的方法
一個方法被調(diào)用得多了,方法體內(nèi)代碼執(zhí)行的次數(shù)自然就多,成為“熱點代碼”是理所當(dāng)然的。
- 被多次執(zhí)行的循環(huán)體
一個方法只被調(diào)用過一次或少量的幾次,但是方法體內(nèi)部存在循環(huán)次數(shù)較多的循環(huán)體,這樣循環(huán)體的代碼也被重復(fù)執(zhí)行多次,因此這些代碼也應(yīng)該認為是“熱點代碼”。
上面提到的多次是一個不具體的詞語,那到底是多少次才能成為熱點代碼呢?
如何檢測熱點代碼
判斷一段代碼是否是熱點代碼,是否需要觸發(fā)即使編譯,這樣的行為稱為熱點探測,熱點探測并不一定知道方法具體被調(diào)用了多少次,目前主要的熱點探測判定方式有兩種:
- 基于采樣的熱點探測:采用這種方法的虛擬機會周期性地檢查各個線程的棧頂如果發(fā)現(xiàn)某個(或某些)方法經(jīng)常出現(xiàn)在棧頂,那這個方法就是“熱點方法”
優(yōu)點:實現(xiàn)簡單高效,容易獲取方法調(diào)用關(guān)系(將調(diào)用堆棧展開即可)
缺點:不精確,容易因為因為受到線程阻塞或別的外界因素的影響而擾亂熱點探測
- 基于計數(shù)器的熱點探測:采用這種方法的虛擬機會為每個方法(甚至是代碼塊)建立計數(shù)器,統(tǒng)計方法的執(zhí)行次數(shù),如果次數(shù)超過一定的閾值就認為它是“熱點方法”
優(yōu)點:統(tǒng)計結(jié)果精確嚴(yán)謹
缺點:實現(xiàn)麻煩,需要為每個方法建立并維護計數(shù)器,不能直接獲取到方法的調(diào)用關(guān)系
HotSpot使用第二種 - 基于計數(shù)器的熱點探測方法。
確定了檢測熱點代碼的方式,如何計算具體的次數(shù)呢?
計數(shù)器的種類(兩種共同協(xié)作)
方法調(diào)用計數(shù)器:這個計數(shù)器用于統(tǒng)計方法被調(diào)用的次數(shù)。默認閾值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次
回邊計數(shù)器:統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù)
了解了熱點代碼和計數(shù)器有什么用呢?達到計數(shù)器的閾值會觸發(fā)后文講解的即時編譯,也就是說即時編譯是需要達到某種條件才會觸發(fā)的,先寫結(jié)論,后文講解什么是即時編譯器。
兩個計數(shù)器的協(xié)作(這里討論的是方法調(diào)用計數(shù)器的情況):當(dāng)一個方法被調(diào)用時,會先檢查該方法是否存在被 JIT(后文講解) 編譯過的版本,如果存在,則優(yōu)先使用編譯后的本地代碼來執(zhí)行。如果不存在已被編譯過的版本,則將此方法的調(diào)用計數(shù)器加 1,然后判斷方法調(diào)用計數(shù)器與回邊計數(shù)器之和是否超過方法調(diào)用計數(shù)器的閾值。如果已經(jīng)超過閾值,那么將會向即時編譯器提交一個該方法的代碼編譯請求。
當(dāng)編譯工作完成之后,這個方法的調(diào)用入口地址就會被系統(tǒng)自動改成新的,下一次調(diào)用該方法時就會使用已編譯的版本。
什么是字節(jié)碼、機器碼、本地代碼?
字節(jié)碼是指平常所了解的 .class 文件,Java 代碼通過 javac 命令編譯成字節(jié)碼
機器碼和本地代碼都是指機器可以直接識別運行的代碼,也就是機器指令
字節(jié)碼是不能直接運行的,需要經(jīng)過 JVM 解釋或編譯成機器碼才能運行
此時你要問了,為什么 Java 不直接編譯成機器碼,這樣不是更快嗎?
1. 機器碼是與平臺相關(guān)的,也就是操作系統(tǒng)相關(guān),不同操作系統(tǒng)能識別的機器碼不同,如果編譯成機器碼那豈不是和 C、C++差不多了,不能跨平臺,Java 就沒有那響亮的口號 “一次編譯,到處運行”;
2.之所以不一次性全部編譯,是因為有一些代碼只運行一次,沒必要編譯,直接解釋運行就可以。而那些“熱點”代碼,反復(fù)解釋執(zhí)行肯定很慢,JVM在運行程序的過程中不斷優(yōu)化,用JIT編譯器編譯那些熱點代碼,讓他們不用每次都逐句解釋執(zhí)行;
3.還有一方面的原因是后文講解的解釋器與編譯器共存的原因。
什么是 JIT ?
為了提高熱點代碼的執(zhí)行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平臺相關(guān)的機器碼,并進行各種層次的優(yōu)化,完成這個任務(wù)的編譯器稱為即時編譯器(Just In Time Compiler),簡稱 JIT 編譯器
什么是編譯和解釋?
編譯器:把源程序的每一條語句都編譯成機器語言,并保存成二進制文件,這樣運行時計算機可以直接以機器語言來運行此程序,速度很快;
解釋器:只在執(zhí)行程序時,才一條一條的解釋成機器語言給計算機來執(zhí)行,所以運行速度是不如編譯后的程序運行的快的;
通過javac命令將 Java 程序的源代碼編譯成 Java 字節(jié)碼,即我們常說的 class 文件。這是我們通常意義上理解的編譯。
字節(jié)碼并不是機器語言,要想讓機器能夠執(zhí)行,還需要把字節(jié)碼翻譯成機器指令。這個過程是Java 虛擬機做的,這個過程也叫編譯。是更深層次的編譯。(實際上就是解釋,引入 JIT 之后也存在編譯)
此時又有疑惑了,Java不是解釋執(zhí)行的嗎?
沒錯,Java 需要將字節(jié)碼逐條翻譯成對應(yīng)的機器指令并且執(zhí)行,這就是傳統(tǒng)的 JVM 的解釋器的功能,正是由于解釋器逐條翻譯并執(zhí)行這個過程的效率低,引入了 JIT 即時編譯技術(shù)。
必須指出的是,不管是解釋執(zhí)行,還是編譯執(zhí)行,最終執(zhí)行的代碼單元都是可直接在真實機器上運行的機器碼,或稱為本地代碼
附一張圖來理解
編譯原理參考:深入分析Java的編譯原理
為何 HotSpot 虛擬機要使用解釋器與編譯器并存的架構(gòu)?
解釋器與編譯器兩者各有優(yōu)勢
解釋器:當(dāng)程序需要迅速啟動和執(zhí)行的時候,解釋器可以首先發(fā)揮作用,省去編譯的時間,立即執(zhí)行。
編譯器:在程序運行后,隨著時間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率。
兩者的協(xié)作:在程序運行環(huán)境中內(nèi)存資源限制較大時,可以使用解釋執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來提升效率。當(dāng)通過編譯器優(yōu)化時,發(fā)現(xiàn)并沒有起到優(yōu)化作用,,可以通過逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行。
即時編譯器與 Java 虛擬機的關(guān)系
即時編譯器并不是虛擬機必需的部分,Java 虛擬機規(guī)范并沒有規(guī)定 Java 虛擬機內(nèi)必須要有即時編譯器的存在,更沒有限定或指導(dǎo)即時編譯器應(yīng)該如何去實現(xiàn)。
但是,即時編譯器編譯性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機優(yōu)秀與否的最關(guān)鍵的指標(biāo)之一。它也是虛擬機中最核心且最能體現(xiàn)虛擬機技術(shù)水平的部分。
即時編譯器的分類
- Client Compiler - C1編譯器
- Server Compiler - C2編譯器
目前主流的 HotSpot 虛擬機(JDK1.7 及之前版本的虛擬機)默認采用一個解釋器和其中一個編譯器直接配合的方式工作,程序使用哪個編譯器,取決于虛擬機運行的模式,就是文章開頭提到的兩種模式。
在 HotSpot 中,解釋器和 JIT 即時編譯器是同時存在的,他們是 JVM 的兩個組件。對于不同類型的應(yīng)用程序,用戶可以根據(jù)自身的特點和需求,靈活選擇是基于解釋器運行還是基于 JIT 編譯器運行。HotSpot 為用戶提供了幾種運行模式供選擇,可通過參數(shù)設(shè)定,分別為:解釋模式、編譯模式、混合模式,HotSpot 默認是混合模式,需要注意的是編譯模式并不是完全通過 JIT 進行編譯,只是優(yōu)先采用編譯方式執(zhí)行程序,但是解釋器仍然要在編譯無法進行的情況下介入執(zhí)行過程。
分層編譯
產(chǎn)生的原因:由于即時編譯器編譯本地代碼需要占用程序運行時間,要編譯出優(yōu)化程度更高的代碼,所花費的時間可能更長;而且要想編譯出優(yōu)化程度更高的代碼,解釋器可能還要替編譯器收集性能監(jiān)控信息,這對解釋執(zhí)行的速度也有影響。為了在程序啟動響應(yīng)速度與運行效率之間達到最佳平衡,HotSpot 虛擬機啟用分層編譯的策略
分層編譯根據(jù)編譯器編譯、優(yōu)化的規(guī)模與耗時,劃分出不同的編譯層次:
第 0 層:程序解釋執(zhí)行,解釋器不開啟性能監(jiān)控功能,可觸發(fā)第 1 層編譯。
第 1 層:也稱為 C1 編譯,將字節(jié)碼編譯為本地代碼,進行簡單,可靠的優(yōu)化,如有必要將加入性能監(jiān)控的邏輯。
第 2 層(或 2 層以上):也稱為 C2 編譯,也是將字節(jié)碼編譯為本地代碼,但是會啟用一些編譯耗時較長的優(yōu)化,甚至?xí)鶕?jù)性能監(jiān)控信息進行一些不可靠的激進優(yōu)化。
實施分層編譯后,Client Compiler 和 Server Compiler 將會同時工作,許多代碼都可能會被多次編譯看,用 Client Compiler 獲取更高的編譯速度,用 Server Compiler 獲取更好的編譯質(zhì)量,在解釋執(zhí)行的時候也無須再承擔(dān)收集性能監(jiān)控信息的任務(wù)。
編譯優(yōu)化技術(shù)
Java 程序員有一個共識,以編譯方式執(zhí)行本地代碼比解釋執(zhí)行方式更快,之所以有這樣的共識,除去虛擬機解釋執(zhí)行字節(jié)碼時額外消耗時間的原因外,還有一個重要的原因就是虛擬機設(shè)計團隊幾乎把對代碼的所有優(yōu)化措施都集中在了即時編譯器中,因此一般來說,即時編譯器產(chǎn)生的本地代碼會比 javac 產(chǎn)生的字節(jié)碼更優(yōu)秀。以下是具有代表性的 HotSpot 虛擬機的即時編譯器在生成代碼時采用的代碼優(yōu)化技術(shù):
- 語言無關(guān)的經(jīng)典優(yōu)化技術(shù)之一:公共子表達式消除
如果一個表達式 E 已經(jīng)計算過了,并且從先前的計算到現(xiàn)在 E 中所有變量的值都沒有發(fā)生變化,那么 E 的這次出現(xiàn)就成為了公共子表達式。對于這種表達式,沒必要花時間再對它進行計算,只需要直接使用前面計算過的表達式結(jié)果代替 E 就可以了。
例子:int d = (c*b) * 12 + a + (a+ b * c) -> int d = E * 12 + a + (a+ E)
- 語言相關(guān)的經(jīng)典優(yōu)化技術(shù)之一:數(shù)組范圍檢查消除
在 Java 語言中訪問數(shù)組元素的時候系統(tǒng)將會自動進行上下界的范圍檢查,超出邊界會拋出異常。對于虛擬機的執(zhí)行子系統(tǒng)來說,每次數(shù)組元素的讀寫都帶有一次隱含的條件判定操作,對于擁有大量數(shù)組訪問的程序代碼,這無疑是一種性能負擔(dān)。Java 在編譯期根據(jù)數(shù)據(jù)流分析可以判定范圍進而消除上下界檢查,節(jié)省多次的條件判斷操作。
- 最重要的優(yōu)化技術(shù)之一:方法內(nèi)聯(lián)
簡單的理解為把目標(biāo)方法的代碼“復(fù)制”到發(fā)起調(diào)用的方法中,消除一些無用的代碼。只是實際的 JVM 中的內(nèi)聯(lián)過程很復(fù)雜,在此不分析。
- 最前沿的優(yōu)化技術(shù)之一:逃逸分析
逃逸分析的基本行為就是分析對象動態(tài)作用域:當(dāng)一個對象在方法中杯定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中,稱為方法逃逸。甚至可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實例變量,稱為線程逃逸。
如果能證明一個對象不會逃逸到方法或線程之外,也就是別的方法或線程無法通過任何途徑訪問到這個對象,則可以為這個變量進行一些高效的優(yōu)化:
- 棧上分配:將不會逃逸的局部對象分配到棧上,那對象就會隨著方法的結(jié)束而自動銷毀,減少垃圾收集系統(tǒng)的壓力。
- 同步消除:如果該變量不會發(fā)生線程逃逸,也就是無法被其他線程訪問,那么對這個變量的讀寫就不存在競爭,可以將同步措施消除掉(同步是需要付出代價的)
- 標(biāo)量替換:標(biāo)量是指無法在分解的數(shù)據(jù)類型,比如原始數(shù)據(jù)類型以及reference類型。而聚合量就是可繼續(xù)分解的,比如 Java 中的對象。標(biāo)量替換如果一個對象不會被外部訪問,并且對象可以被拆散的話,真正執(zhí)行時可能不創(chuàng)建這個對象,而是直接創(chuàng)建它的若干個被這個方法使用到的成員變量來代替。這種方式不僅可以讓對象的成員變量在棧上分配和讀寫,還可以為后后續(xù)進一步的優(yōu)化手段創(chuàng)建條件。
按自己理解整理的,知識點順序不知是否合適,還請大家指導(dǎo)。
參考來源:
《深入理解 Java 虛擬機》