您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么使用java Hotspot虛擬機(jī)內(nèi)的即時(shí)編譯器”,在日常操作中,相信很多人在怎么使用java Hotspot虛擬機(jī)內(nèi)的即時(shí)編譯器問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”怎么使用java Hotspot虛擬機(jī)內(nèi)的即時(shí)編譯器”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
在部分商用虛擬機(jī)(SunHotspot、IBMJ9)中,Java程序最初是通過(guò)解釋器解釋執(zhí)行的,當(dāng)虛擬機(jī)發(fā)現(xiàn)有個(gè)方法或代碼塊運(yùn)行特別頻繁時(shí),就會(huì)把這些代碼認(rèn)定為“熱點(diǎn)代碼”。為了提高熱點(diǎn)代碼的執(zhí)行效率,虛擬機(jī)會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼,并進(jìn)行各種層次的優(yōu)化,完成這個(gè)任務(wù)的編譯器被稱為即時(shí)編譯器(JustInTimeCompiler,簡(jiǎn)稱JIT編譯器)。即時(shí)編譯器并不是虛擬機(jī)必須的部分,但是即時(shí)編譯器編譯性能的好壞、代碼優(yōu)化程度的高低卻是衡量一款商用虛擬機(jī)好壞與否最關(guān)鍵的指標(biāo)之一,是虛擬機(jī)中最核心且最能體現(xiàn)虛擬機(jī)技術(shù)水平的部分。
重點(diǎn)我們需要關(guān)注解決以下幾個(gè)問(wèn)題:
為何Hotspot虛擬機(jī)要使用解釋器和編譯器并存的架構(gòu)?
為何Hotspot虛擬機(jī)要實(shí)現(xiàn)兩個(gè)不同的即時(shí)編譯器?
程序何時(shí)使用解釋器執(zhí)行?何時(shí)使用編譯器執(zhí)行?
哪些程序代碼會(huì)被編譯為本地代碼?如何編譯為本地代碼?
如何從外部觀察即時(shí)編譯器的編譯過(guò)程和編譯結(jié)果?
盡管不是所有的Java虛擬機(jī)都采樣解釋器與編譯器并存的架構(gòu),但是許多主流的虛擬機(jī),比如SunHotspot、IBMJ9,都同時(shí)包含解釋器與編譯器。解釋器與編譯器有各自的優(yōu)勢(shì):當(dāng)程序需要快速啟動(dòng)時(shí),解釋器可以發(fā)揮作用,省去編譯時(shí)間立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來(lái)越多的代碼編譯成本地代碼后,可以獲取更高的執(zhí)行效率。
當(dāng)程序運(yùn)行環(huán)境的內(nèi)存資源限制較大時(shí),使用解釋器執(zhí)行節(jié)省內(nèi)存,反之可以使用編譯執(zhí)行提升效率。同時(shí),解釋器還可以作為編譯器激進(jìn)優(yōu)化時(shí)的一個(gè)“逃生門”,讓編譯器根據(jù)概率選擇一個(gè)大多數(shù)時(shí)候都能提升運(yùn)行速度的優(yōu)化手段,當(dāng)激進(jìn)優(yōu)化的假設(shè)不成立時(shí),比如加載了新類后類型繼承結(jié)構(gòu)出現(xiàn)變化,出現(xiàn)“罕見(jiàn)陷阱”時(shí)可以通過(guò)逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行。因此,在虛擬機(jī)中解釋器和編譯器經(jīng)常配合工作,如下圖所示:
圖-解釋器與編譯器的交互
HotSpot虛擬機(jī)內(nèi)置了兩個(gè)即時(shí)編譯器:ClientCompiler和ServerCompiler,簡(jiǎn)稱C1編譯器和C2編譯器。默認(rèn)采用解釋器和其中一個(gè)編譯器直接配合的方式工作,具體使用哪個(gè)編譯器,取決于虛擬機(jī)工作的模式,用戶可以使用-client參數(shù)或-server參數(shù)指定虛擬機(jī)的工作模式,還可以使用-Xint強(qiáng)制虛擬機(jī)運(yùn)行于“解釋模式”。
由于即時(shí)編譯器編譯本地代碼需要占用程序運(yùn)行時(shí)間,要編譯出優(yōu)化程度更高的代碼,所需時(shí)間會(huì)更長(zhǎng)。同時(shí),解釋器還要替編譯器收集性能監(jiān)控信息,這對(duì)解釋執(zhí)行速度也有影響。為了在程序啟動(dòng)響應(yīng)速度與運(yùn)行效率之間達(dá)到最佳平衡,HotSpot虛擬機(jī)又引入了分層編譯的策略。分層編譯根據(jù)編譯器編譯、優(yōu)化的規(guī)模與耗時(shí),劃分為不同的編譯層次,包括:
第0層,程序解釋執(zhí)行,不開(kāi)啟性能監(jiān)控功能,可觸發(fā)第1層編譯。
第1層,稱為C1編譯,將字節(jié)碼編譯為本地代碼,并進(jìn)行簡(jiǎn)單可靠的優(yōu)化,如有必要將加入性能監(jiān)控邏輯。
第2層,稱為C2編譯,也是將字節(jié)碼編譯為本地代碼,但是會(huì)進(jìn)行耗時(shí)較長(zhǎng)的優(yōu)化,甚至?xí)鶕?jù)性能監(jiān)控信息進(jìn)行一些不完全可靠的激進(jìn)優(yōu)化。
實(shí)施分層編譯后,ClientCompiler和ServerCompiler會(huì)同時(shí)工作,許多代碼可能會(huì)被編譯多次,用ClientCompiler獲得更快的編譯速度,用ServerCompiler獲取更好的編譯質(zhì)量,在解釋執(zhí)行的時(shí)候也無(wú)需再承擔(dān)收集性能監(jiān)控信息的任務(wù)。
在運(yùn)行過(guò)程中,會(huì)被即時(shí)編譯器編譯的熱點(diǎn)代碼有兩類:
被多次調(diào)用的方法。
被多次執(zhí)行的循環(huán)體。
這兩種情況,編譯器都會(huì)編譯整個(gè)方法。因?yàn)榫幾g發(fā)生在方法執(zhí)行過(guò)程中,因此形象地稱之為棧上替換(OnStackReplacement,簡(jiǎn)稱OSR,即方法棧幀還在棧上,方法就被替換了)。
判斷一段代碼是不是熱點(diǎn)代碼,是否需要觸發(fā)即時(shí)編譯,這樣的行為稱為熱點(diǎn)探測(cè)(HotSpotDetection),熱點(diǎn)探測(cè)方式主要有兩種:
基于采樣的熱點(diǎn)探測(cè):采用這種方法的虛擬機(jī)會(huì)周期性地檢查各個(gè)線程的棧頂,如果某個(gè)方法經(jīng)常出現(xiàn)在棧頂,那它就是熱點(diǎn)方法。其優(yōu)點(diǎn)是簡(jiǎn)單、高效,還可以獲取方法調(diào)用關(guān)系;缺點(diǎn)是不夠精確,容易受到線程阻塞或其他外接因素的影響。
基于計(jì)數(shù)的熱點(diǎn)探測(cè):采用這種方法的虛擬機(jī)會(huì)為每個(gè)方法(甚至是代碼塊)建立計(jì)數(shù)器,統(tǒng)計(jì)方法執(zhí)行次數(shù),次數(shù)超過(guò)一定閾值就認(rèn)為是熱點(diǎn)方法。這種方法實(shí)現(xiàn)起來(lái)麻煩,但是其統(tǒng)計(jì)結(jié)果相對(duì)來(lái)說(shuō)更加精確和嚴(yán)謹(jǐn)。
在HotSpot虛擬機(jī)里使用的是第二種方法,因此它為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(InvocationCounter)和回邊計(jì)數(shù)器(BackedgeCounter)。在確定虛擬機(jī)運(yùn)行參數(shù)的情況下,這兩個(gè)計(jì)數(shù)器都有一定的閾值,超過(guò)閾值就會(huì)觸發(fā)JIT編譯。
Java虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)幾乎對(duì)代碼的所有優(yōu)化措施都集中在了即時(shí)編譯器中,因此一般來(lái)說(shuō),即時(shí)編譯器產(chǎn)生的本地代碼會(huì)比javac產(chǎn)生的字節(jié)碼更加優(yōu)秀。下面,我們介紹一些HotSpot虛擬機(jī)即時(shí)編譯器生成代碼時(shí)采用的代碼優(yōu)化技術(shù)。
公共子表達(dá)式消除是一個(gè)普遍應(yīng)用于各種編譯器的經(jīng)典優(yōu)化技術(shù),它的原理是:如果一個(gè)表達(dá)式E已經(jīng)計(jì)算過(guò)了,并且從先前計(jì)算到現(xiàn)在E中所有變量的值都沒(méi)有發(fā)生變化,那么E的這次計(jì)算就稱為公共子表達(dá)。對(duì)于這種表達(dá)式,就沒(méi)有必要再對(duì)其進(jìn)行計(jì)算了,使用之前計(jì)算過(guò)的值即可。
數(shù)組邊界檢查消除是即時(shí)編譯器中語(yǔ)言相關(guān)的經(jīng)典優(yōu)化技術(shù)。如果有一個(gè)數(shù)組foo[],Java語(yǔ)言在訪問(wèn)數(shù)組元素foo[i]時(shí),系統(tǒng)會(huì)自動(dòng)進(jìn)行上下界的范圍檢查,即i的取值范圍是0~foo.length-1,否則將拋出運(yùn)行時(shí)異常java.lang.ArrayIndexOutOfBoundsException。這對(duì)開(kāi)發(fā)者來(lái)說(shuō)是好事情,即時(shí)程序員沒(méi)有專門編寫(xiě)防御代碼,也可以避免大部分的溢出攻擊。但是,對(duì)于虛擬機(jī)的執(zhí)行子系統(tǒng)來(lái)說(shuō),每次數(shù)組元素的讀寫(xiě)操作都帶有一次隱含的條件判定操作,對(duì)于擁有大量數(shù)組訪問(wèn)的系統(tǒng),無(wú)疑是一種性能上的負(fù)擔(dān)。
編譯器會(huì)對(duì)代碼進(jìn)行分析,如果確定某次數(shù)組訪問(wèn)一定不會(huì)越界,就可以去掉數(shù)組的上下界檢查。比如在循環(huán)訪問(wèn)數(shù)組時(shí),編譯器只要通過(guò)數(shù)據(jù)流分析確定循環(huán)變量的取值范圍一定在[0,foo.length)之間,就可以在整個(gè)循環(huán)中把數(shù)組上下界檢查消除。
與數(shù)組邊界檢查消除類似的優(yōu)化,還有隱式異常處理,Java中空指針檢查和除數(shù)為零檢查都采用了這種思路。
方法內(nèi)聯(lián)看起來(lái)簡(jiǎn)單,但實(shí)際中很多方法都無(wú)法直接進(jìn)行內(nèi)聯(lián)。原因是除了使用invokespecial指令調(diào)用的私有方法、實(shí)例構(gòu)造器、父類方法以及使用invokestatic指令調(diào)用的靜態(tài)方法,還有部分final方法能夠在編譯時(shí)唯一確定執(zhí)行的方法版本,其他都可能存在多于一個(gè)版本的方法接收者,需要在運(yùn)行時(shí)才能確定,這一類方法稱為虛方法,由于Java語(yǔ)言提倡使用面向?qū)ο蟮木幊谭绞竭M(jìn)行編程,而Java語(yǔ)言默認(rèn)的實(shí)例方法就是虛方法,因此內(nèi)聯(lián)與虛方法之間產(chǎn)生矛盾。
對(duì)于一個(gè)虛方法,編譯期做內(nèi)聯(lián)的時(shí)候根本無(wú)法確定應(yīng)該使用哪個(gè)方法的版本,為了解決虛方法的內(nèi)聯(lián)問(wèn)題,Java虛擬機(jī)引入了一種稱為“類型繼承關(guān)系分析”(ClassHierarchyAnalysis,CHA)的技術(shù),這是一種基于整個(gè)應(yīng)用程序的類型分析技術(shù),它用于確定在目前已加載的類中,某個(gè)接口是否有多于一種實(shí)現(xiàn),某個(gè)類是否存在子類、子類是否為抽象類等信息。
非虛方法可以直接內(nèi)聯(lián),如果是虛方法且通過(guò)CHA分析得知某個(gè)方法只有一個(gè)版本那也可以進(jìn)行內(nèi)聯(lián),不過(guò)這種內(nèi)聯(lián)屬于“激進(jìn)優(yōu)化”,需要預(yù)留一個(gè)“逃生門”,稱為守護(hù)內(nèi)聯(lián)。如果程序在執(zhí)行過(guò)程中,虛擬機(jī)一直沒(méi)有加載到令這個(gè)類繼承關(guān)系發(fā)生變化的類,那這個(gè)內(nèi)聯(lián)優(yōu)化的代碼就可以一直使用下去。但如果加載了導(dǎo)致繼承關(guān)系發(fā)生變化的新類,那就需要拋棄已經(jīng)編譯的代碼,返回到解釋狀態(tài)執(zhí)行,或者重新進(jìn)行編譯。
如果CHA查出來(lái)某方法有多個(gè)版本,則編譯器還會(huì)進(jìn)行最后一次努力,使用內(nèi)聯(lián)緩存InlineCache來(lái)完成方法內(nèi)聯(lián),這是一個(gè)建立在目標(biāo)方法正常入口之前的緩存,其工作原理是:在未發(fā)生方法調(diào)用之前,內(nèi)聯(lián)緩存為空,當(dāng)?shù)谝淮握{(diào)用發(fā)生后,緩存記錄下方法接收者的版本信息。后續(xù)每次執(zhí)行都檢查版本,如果以后進(jìn)來(lái)的每次調(diào)用的方法接受者版本都是一樣的,那么這個(gè)內(nèi)聯(lián)還可以一直使用下去,否則查找虛方法表進(jìn)行方法分派。
逃逸分析是目前Java虛擬機(jī)中比較前沿的優(yōu)化技術(shù),它與類型繼承關(guān)系分析一樣,并不是直接優(yōu)化代碼的技術(shù),而是為其他優(yōu)化手段提供依據(jù)的分析技術(shù)。逃逸分析的基本行為是分析對(duì)象動(dòng)態(tài)作用域:當(dāng)一個(gè)對(duì)象在方法里定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中,稱為方法逃逸。甚至還有可能被外部線程訪問(wèn)到,譬如賦值給類變量或其他線程中訪問(wèn)的實(shí)例變量,稱為線程逃逸。
如果能證明一個(gè)對(duì)象不會(huì)逃逸到方法或線程之外,就可以為這個(gè)變量進(jìn)行一些高效優(yōu)化:
棧上分配:Java一般是在堆上分配對(duì)象的,對(duì)象的回收依賴虛擬機(jī)的垃圾收集系統(tǒng),垃圾收集系統(tǒng)回收和整理內(nèi)存都需要耗費(fèi)時(shí)間。如果一個(gè)對(duì)象不會(huì)逃逸出方法之外,那讓這個(gè)對(duì)象在棧上分配會(huì)是一個(gè)不錯(cuò)的主意,對(duì)象所占用的內(nèi)存空間可以隨著棧幀出棧而銷毀,減輕了垃圾收集系統(tǒng)的壓力。
同步消除:線程同步本身是一個(gè)相對(duì)耗時(shí)的過(guò)程,如果逃逸線程分析確定一個(gè)對(duì)象不會(huì)逃逸出線程,不會(huì)被其他線程訪問(wèn),那這個(gè)變量的讀寫(xiě)肯定不會(huì)有競(jìng)爭(zhēng),對(duì)這個(gè)變量實(shí)施的同步措施也就可以消除掉。
標(biāo)量替換:標(biāo)量是指一個(gè)數(shù)據(jù)已經(jīng)無(wú)法再分解成更小的數(shù)據(jù)了,Java中的原始數(shù)據(jù)類型都不能再進(jìn)一步分解,就可以稱為標(biāo)量。相對(duì)的,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,就可以稱作聚合量,Java中的對(duì)象就是典型的聚合量。如果把一個(gè)Java對(duì)象拆散,根據(jù)程序訪問(wèn)情況,將其使用到的成員變量恢復(fù)原始類型來(lái)訪問(wèn)就叫做標(biāo)量替換。如果逃逸分析證明一個(gè)對(duì)象不會(huì)被外部訪問(wèn),并且這個(gè)對(duì)象可以被拆散的話,那程序真正執(zhí)行的時(shí)候就可能不創(chuàng)建這個(gè)對(duì)象,改為直接創(chuàng)建它的若干個(gè)成員變量來(lái)代替。將對(duì)象拆分后,除了可以讓成員變量在棧上分配和讀寫(xiě)外,還可以為后續(xù)進(jìn)一步優(yōu)化創(chuàng)建條件。
需要注意逃逸分析的性能收益是否低于它的消耗。如果要完全準(zhǔn)確地判斷一個(gè)對(duì)象是否會(huì)逃逸,需要進(jìn)行數(shù)據(jù)流敏感的一系列復(fù)雜分析,從而確定程序各個(gè)分支執(zhí)行時(shí)對(duì)此對(duì)象的影響,這是一個(gè)相對(duì)耗時(shí)的過(guò)程,如果分析發(fā)現(xiàn)沒(méi)有幾個(gè)不逃逸的對(duì)象,那這些運(yùn)行期耗用的時(shí)間就白白浪費(fèi)了,所以目前虛擬機(jī)只能采用不那么準(zhǔn)確,但時(shí)間壓力相對(duì)較小的算法來(lái)完成逃逸分析。
到此,關(guān)于“怎么使用java Hotspot虛擬機(jī)內(nèi)的即時(shí)編譯器”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。