前言
前面介紹過
JVM被分為三個主要的子系統(tǒng):
- 類加載器子系統(tǒng)
- 運行時數(shù)據(jù)區(qū)(也就是內(nèi)存相關(guān))
執(zhí)行引擎
前幾章我們簡單的梳理了一下JVM的類加載機制及運行時數(shù)據(jù)區(qū),
今天我們來聊聊JVM執(zhí)行引擎.
如無特殊說明, 所有描述JVM的特性均特指HotSpot VM
Java程序的執(zhí)行過程
簡單回顧下Java程序的執(zhí)行過程
-
編譯 (Java前端編譯器)
將Java文件編譯為.class字節(jié)碼文件, 這部分工作由Java前端編譯器完成, 與JVM本身其實沒什么關(guān)系;
編譯過程如下圖
加載(JVM-類加載器)
負(fù)責(zé)"裝載字節(jié)碼", 但字節(jié)碼并不能夠直接運行在操作系統(tǒng)之上, 因為字節(jié)碼指令并非等價于本地機器指令, 它內(nèi)部包含的僅僅只是一些能夠被JVM鎖識別的字節(jié)碼指令、符號表和其他輔助信息.運行(JVM-執(zhí)行引擎)
Java字節(jié)碼的執(zhí)行是由JVM執(zhí)行引擎來完成
執(zhí)行引擎
執(zhí)行引擎的任務(wù)就是將字節(jié)碼指令解釋/編譯為對應(yīng)平臺上的本地機器指令.
簡單來說,JVM中的執(zhí)行引擎充當(dāng)了將高級語言翻譯為機器語言的翻譯官.
科普(翻譯官)
正式介紹執(zhí)行引擎前, 我們先了解下現(xiàn)實世界的翻譯官這個職業(yè).
看過一些跨國演講節(jié)目的都知道, 一般都會配多個翻譯官,來實現(xiàn)語言互通.
細(xì)心的朋友可能會發(fā)現(xiàn), 翻譯的形式一般有兩種
交替?zhèn)髯g
領(lǐng)導(dǎo)講一句, 翻譯官翻譯一句, 領(lǐng)導(dǎo)繼續(xù)講, 如此往復(fù)同聲傳譯
領(lǐng)導(dǎo)講的同時, 翻譯官也同時翻譯, 基本上領(lǐng)導(dǎo)講完, 翻譯也完成了
不難看出, 同聲傳譯是實效性比較高的方式, 同時對翻譯官的能力、壓力也是極大的.
而交替?zhèn)髯g則顯得從容了很多, 不過實效性就差了很多.
那么, 有沒有辦法可以讓同聲傳譯的門檻降低點呢? 讓普通點的翻譯官都能勝任這樣的工作呢?!
聰明的小伙伴肯定想到了:
如果能夠提前知道大佬演講的內(nèi)容, 提前翻譯好個大概, 不就完美解決這個問題了么?!
理想情況下, 是外國人照著稿子讀, 翻譯官按著提前準(zhǔn)備好的翻譯稿讀出來.
實際情況下, 這種照本宣科的場景還是比較少的, 大部分情況下是演講者會有個大綱, 翻譯官也會提前拿到, 然后真正演講時外國人順著大綱穿插自由發(fā)揮, 這樣, 翻譯官需要注意的其實就是自由發(fā)揮的那部分內(nèi)容, 這將大大的減輕了翻譯的壓力.
PS: 更真實通用的情況是:
翻譯官憑借自身的老練和職場經(jīng)驗, 能夠熟悉的掌握一些話術(shù)的翻譯, 這些話術(shù)來自于多年的工作經(jīng)驗.
比如正式的演講“尊敬的各位.....”, 這些其實是通用的標(biāo)準(zhǔn)的東西, 翻譯官完全可以提前準(zhǔn)備好;
或者, 翻譯官和演講者配合多年, 很有默契, 完全掌握演講者的演講習(xí)慣勇垛、話術(shù), 這種默契將有助于翻譯官提前預(yù)知演講者接下來的發(fā)言.
言歸正傳, 我們開始了解執(zhí)行引擎這個“字節(jié)碼翻譯官”的翻譯方式.
- 字節(jié)碼解釋器 (交替?zhèn)髯g)
當(dāng)Java虛擬機啟動時會根據(jù)預(yù)定義的規(guī)范對字節(jié)碼采用逐行編譯的方式執(zhí)行,將每條字節(jié)碼文件中的內(nèi)容“翻譯”為對應(yīng)平臺的本地機器指令并執(zhí)行.
- JIT編譯器 (同聲傳譯)
JVM將源碼直接編譯為和本地機器平臺相關(guān)的機器語言,但是并不會立刻執(zhí)行.
JIT編譯器在運行時會針對那些頻繁被調(diào)用的“熱點代碼”做出深度優(yōu)化,將其直接編譯為對應(yīng)平臺的本地機器指令,以此提升Java程序的執(zhí)行性能.即時編譯的目的是避免函數(shù)被解釋執(zhí)行, 而是將整個函數(shù)體編譯成為機器碼,每次函數(shù)執(zhí)行時,只執(zhí)行編譯后的機器碼即可,這種方式可以使執(zhí)行效率大幅度提升.
簡單的看完以上兩種方式, 不難看出, 其實JVM執(zhí)行引擎這個“翻譯官”的翻譯方式和現(xiàn)實中的翻譯官沒什么區(qū)別.
早先, JVM執(zhí)行引擎其實只有“字節(jié)碼解釋器 ”這一種方式, 效率低下, 和C世吨、C++這些語言的執(zhí)行效率簡直無法相比;
直到后來引入了JIT編譯器, 一方面將一些函數(shù)、固定的代碼提前編譯好;
另一方面針對一些不固定的則繼續(xù)保持解釋執(zhí)行的方式; JVM把兩者搭配使用, 極大的提升了整體的效率.
熱點代碼及探測方式
JVM執(zhí)行引擎是采用了解釋執(zhí)行+編譯執(zhí)行的混合方式.
PS: 這也就是我們傻傻分不清楚JAVA究竟是解釋型語言還是編譯型語言的原因.
同時我們也提到了執(zhí)行引擎中的JIT即時編譯, 關(guān)于JIT即時編譯有個很重要的概念, 即熱點代碼.
JIT只會對熱點代碼進行編譯
熱點代碼
一個被多次調(diào)用的方法,或者是一個方法體內(nèi)部循環(huán)次數(shù)較多的循環(huán)體都可以被稱之為“熱點代碼”.
因此都可以通過JIT編譯器編譯為本地機器指令.
由于這種編譯方式發(fā)生在方法的執(zhí)行過程中驶臊,因此也被稱之為棧上替換挪挤,或簡稱為OSR (On StackReplacement)編譯.PS: 熱點代碼經(jīng)過JIT即時編譯后成為機器指令,需要緩存起來Code Cache,存放在方法區(qū)(元空間/本地內(nèi)存)
一個方法究竟要被調(diào)用多少次,或者一個循環(huán)體究竟需要執(zhí)行多少次循環(huán)才可以達到這個標(biāo)準(zhǔn)?
必然需要一個明確的閾值,JIT編譯器才會將這些“熱點代碼”編譯為本地機器指令執(zhí)行.
這里主要依靠熱點探測功能.
熱點探測
目前HotSpot VM所采用的熱點探測方式是基于計數(shù)器的熱點探測.
采用基于計數(shù)器的熱點探測,HotSpot VM將會為每一個 方法都建立2個不同類型的計數(shù)器,分別為
- 方法調(diào)用計數(shù)器(Invocation Counter)
- 回邊計數(shù)器(BackEdge Counter).
方法調(diào)用計數(shù)器用于統(tǒng)計方法的調(diào)用次數(shù), 回邊計數(shù)器則用于統(tǒng)計循環(huán)體執(zhí)行的循環(huán)次數(shù).
- 方法調(diào)用計數(shù)器
這個計數(shù)器就用于統(tǒng)計方法被調(diào)用的次數(shù),它的默認(rèn)閾值在Client 模式下是1500 次, 在Server 模式下是10000 次.
超過這個閾值,就會觸發(fā)JIT編譯.
這個閾值可以通過虛擬機參數(shù)來人為設(shè)定.-XX :CompileThreshold
當(dāng)一個方法被調(diào)用時,會先檢查該方法是否存在被JIT編譯過的版本,
如果存在,則優(yōu)先使用編譯后的本地代碼來執(zhí)行.
如果不存在已被編譯過的版本,則將此方法的調(diào)用計數(shù)器值加1,然后判斷方法調(diào)用計數(shù)器與回邊計>數(shù)器值之和是否超過方法調(diào)用計數(shù)器的閾值.
如果已超過閾值,那么將會向即時編譯器提交一個該方法的代碼編譯請求.
- 回邊計數(shù)器
它的作用是統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊” (Back Edge).
顯然,建立回邊計數(shù)器統(tǒng)計的目的就是為了觸發(fā)OSR編譯.
熱度衰減
如果不做任何設(shè)置,方法調(diào)用計數(shù)器統(tǒng)計的并不是方法被調(diào)用的絕對次數(shù),而是一個相對的執(zhí)行頻率,即一段時間之內(nèi)方法被調(diào)用的次數(shù).
當(dāng)超過一定的時間限度,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時編譯器編譯,那這個方法的調(diào)用計數(shù)器就會被減少一半,這個過程稱為方法調(diào)用計數(shù)器熱度的衰減(Counter Decay),而這段時間就稱為此方法統(tǒng)計的半衰周期(Counter Half Life Time);
進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,
可以使用虛擬機參數(shù) -XX:-UseCounterDecay來關(guān)閉熱度衰減,讓方法計數(shù)器統(tǒng)計方法調(diào)用的絕對次數(shù);這樣,只要系統(tǒng)運行時間足夠長,絕大部分方法都會被編譯成本地代碼.
另外,可以使用-XX:CounterHalfLifeTime參數(shù)設(shè)置半衰周期的時間,單位是秒.
其他熱點探測技術(shù)
HotSpot中的熱點代碼探測是基于計數(shù)器模式實現(xiàn)的, 但是除了計數(shù)器的方式探測之外, 還可以基于采樣(sampling)以及蹤跡(Trace)模式對代碼進行熱點探測.
采樣探測
采用這種探測技術(shù)的虛擬機會周期性的檢查每個線程的虛擬機棧棧頂,如果一些在檢查時經(jīng)常出現(xiàn)在棧頂?shù)姆椒?那么就代表這個方法經(jīng)常被調(diào)用執(zhí)行,對于這類方法可以判定為熱點方法.
- 優(yōu)點:實現(xiàn)簡單,可以很輕松的判定出熱度很高(調(diào)用次數(shù)頻繁)的方法
- 缺點:無法實現(xiàn)精準(zhǔn)探測,因為檢查是周期性的,并且有些方法中存在線程阻塞、休眠等因素,會導(dǎo)致有些方法無法被精準(zhǔn)檢測
蹤跡探測
采用這種方式的虛擬機是將一段頻繁執(zhí)行的代碼作為一個編譯單元,并僅對該單元進行編譯,該單元由一個線性且連續(xù)的指令集組成,僅有一個入口关翎,但有多個出口.
也就代表著:基于蹤跡而編譯的熱點代碼不僅僅局限在一個單獨的方法或者代碼塊中,一條蹤跡可能對應(yīng)多個方法,代碼中頻繁執(zhí)行的路徑就可能被識別成不同的蹤跡.
- 優(yōu)點:這種方式實現(xiàn)可以使得熱點探測擁有更高精度,可以避免將一塊代碼塊中所有的代碼都進行編譯的情況出現(xiàn),能夠在很大程序上減少不必要的編譯開銷.
因為無論是采樣探測還是計數(shù)器探測的方式,都是以方法體或循環(huán)體作為編譯的基本單元的. - 缺點:蹤跡探測的實現(xiàn)過程非常復(fù)雜,難度非常高.
HotSpot虛擬機采用的計數(shù)探測的方式,實現(xiàn)難度扛门、編譯開銷與探測精準(zhǔn)三者之間會有一個很好的權(quán)衡.
三種探測技術(shù)比較如下:
- 實現(xiàn)難度:采樣探測 < 計數(shù)探測 < 蹤跡探測
- 探測精度:采樣探測 < 計數(shù)探測 < 蹤跡探測
- 編譯開銷:蹤跡探測 < 計數(shù)探測 < 采樣探測
JVM為何不移除解釋器?
很多同學(xué)可能會有疑惑, 如果以純JIT編譯器的方式執(zhí)行,性能方面絕對會超出解釋器+編譯器混合的模式,但為何虛擬機至今也不移除解釋器,還要用解釋器來拖累Java程序的性能呢?
其實主要有兩個原因:
- 保證Java的絕對跨平臺性
如果將解釋器從虛擬機中移除就代表著:
每到一個不同的平臺,比如從Windows遷移到Linux環(huán)境,那么JIT又要重新編譯生成對應(yīng)平臺的機器碼指令才能讓Java程序執(zhí)行
- 保證啟動速度
如果移除了解釋器模塊,那么就代表著所有的字節(jié)碼指令需要在啟動時全部先編譯為本地的機械碼,這樣才能使得Java程序能夠正常執(zhí)行.
此時時間開銷是巨大的,那么會導(dǎo)致一些需要緊急上線的項目可能編譯都需要等半天的時間
PS: 的確有JVM的實現(xiàn)移除了解釋器模塊, 就是號稱“史上最快”的JRockitVM
總結(jié)
HotSpot中采用的是解釋器+JIT即時編譯器混合.
好處
在Java程序運行時,JVM可以快速啟動,前期先由解釋器發(fā)揮作用,不需要等到編譯器把所有字節(jié)碼指令編譯完之后才執(zhí)行,這樣可以省去很大一部分的編譯時間;
后續(xù)隨著程序在線上運行的時間越來越久,JIT發(fā)揮作用,慢慢的將一些程序中的熱點代碼替換為本地機器碼運行,這樣可以讓程序的執(zhí)行效率更高.
同時,因為HotSpotVM中存在熱度衰減的概念,所以當(dāng)一段代碼的熱度下降時,JIT會取消對它的編譯,重新更換為解釋器執(zhí)行的模式工作, HotSpot的這種執(zhí)行模式也被成為“自適應(yīng)優(yōu)化”執(zhí)行.
當(dāng)然,我們在程序啟動時也可以通過JVM參數(shù)自己指定執(zhí)行模式
- -Xint:完全采用解釋器模式執(zhí)行程序
- -Xcomp:完全采用即時編譯器模式執(zhí)行程序. 如果即時編譯器出現(xiàn)問題,解釋器會介入執(zhí)行
- -Xmixed:采用解釋器+JIT即時編譯器的混合模式共同執(zhí)行(默認(rèn)的執(zhí)行方式)
HotSpot VM 中的JIT分類
在HotSpot VM中內(nèi)嵌有兩個JIT編譯器
Client Compiler(簡稱C1)
Server Compiler(簡稱C2)
C1編譯器(Client Compiler)
C1編譯器主要追求穩(wěn)定和編譯速度,屬于保守派.
C1中常見的優(yōu)化方案有幾種
- 公共子表達式消除
如果一個表達式E已經(jīng)計算過了,并且從先前的計算到現(xiàn)在E中所有變量的值都沒有發(fā)生變化,那E的這次出現(xiàn)就成公共子表達式,可以用原先的表達式進行消除,直接使用上次的計算結(jié)果,無需再次計算
- 方法內(nèi)聯(lián)
將引用的方法代碼編譯到引用點處,這樣可以減少棧幀的生成,減少參數(shù)傳遞以及跳轉(zhuǎn)過程
- 去虛擬化
對唯一的實現(xiàn)類進行內(nèi)聯(lián)
- 冗余消除
通過對字節(jié)碼指令進行流分析,將一些運行過程中不會執(zhí)行的代碼消除
- 空檢測消除
將顯式調(diào)用的NullCheck(空指針判斷)擦除,改成ImplicitNullCheck異常信號機制處理- 自動裝箱消除
對于一些不必要的裝箱操作會被消除,比如剛裝箱的數(shù)據(jù)又在后面立馬被拆箱,這種無用操作就會被消除- 安全點消除
對于線程無法抵達或不會停留的安全點會進行消除- 反射消除
對于一些可以正常訪問無需通過反射機制獲取的數(shù)據(jù),會被改為直接訪問,消除反射操作
C2編譯器(Server Compiler)
C2編譯器則主要是追求編譯后的執(zhí)行性能,屬于激進派.
C2編譯器建立在C1編譯器的基礎(chǔ)優(yōu)化之上,除了使用C1中的優(yōu)化手段之外,還有幾種基于逃逸分析的激進優(yōu)化手段
- 標(biāo)量替換
用標(biāo)量值代替聚合對象的屬性值
- 棧上分配
對于未逃逸的對象分配對象在棧而不是堆
- 同步消除
清除同步操作,通常指synchronized
逃逸分析
Java 中對象的創(chuàng)建一般會由堆內(nèi)存去分配內(nèi)存空間來進行存儲,在堆內(nèi)存空間不足的時候,GC 便會對堆內(nèi)存進行垃圾回收.
如果 GC 運行的次數(shù)過多,便會影響程序的性能,所以 “逃逸分析” 由此誕生.
它的目的就是判斷哪些對象是可以存儲在棧內(nèi)存中而不用存儲在堆內(nèi)存中,從而讓其隨著線程的消逝而消逝,進而減少 GC 發(fā)生的頻率.
簡而言之, 逃逸分析就是Hotspot 虛擬機可以分析新創(chuàng)建對象的使用范圍,并決定是否在 Java 堆上分配內(nèi)存的一項技術(shù).
逃逸分析是建立在方法為單位之上的,如果一個成員對象在方法體中產(chǎn)生,但是直至方法結(jié)束也沒有走出方法體的作用域, 那么該成員就可以被理解為未逃逸.
反之,如果一個成員在方法最后被“return”出去了, 或在方法體的邏輯中被賦值給了外部成員,那么則代表著該成員逃逸了.
逃逸的方式
- 方法逃逸
一個對象在方法中被定義,但卻被方法以外的其他代碼使用.
在一個方法體內(nèi),定義一個局部變量,而它可能被外部方法引用.
比如作為調(diào)用參數(shù)傳遞給方法,或作為對象直接返回
可以理解為對象跳出了方法
- 線程逃逸
一個對象由某個線程在方法中被定義,但卻被其他線程訪問
這個對象被其他線程訪問到,比如賦值給了實例變量,并被其他線程訪問到了.
可以理解為對象逃出了當(dāng)前線程
逃逸的狀態(tài)
一個對象有三種逃逸狀態(tài)
- 全局逃逸
一個對象的作用范圍逃出了當(dāng)前方法或者當(dāng)前線程
一般有以下幾種場景
- 對象是一個靜態(tài)變量
- 對象是一個已經(jīng)發(fā)生逃逸的對象
- 對象作為當(dāng)前方法的返回值
- 參數(shù)逃逸
一個對象被作為方法參數(shù)傳遞或者被參數(shù)引用,但在調(diào)用過程中不會發(fā)生全局逃逸,這個狀態(tài)是通過被調(diào)方法的字節(jié)碼確定的
- 沒有逃逸
方法中的對象沒有發(fā)生逃逸
逃逸分析的好處
逃逸分析的作用,就是篩選出沒有發(fā)生逃逸的對象,從而對它們進行以下三方面的優(yōu)化
- 同步消除(鎖消除)
如果你定義的類的方法上有同步鎖,但在運行時,卻只有一個線程在訪問,此時逃逸分析后的機器碼,會去掉同步鎖運行
標(biāo)量替換
Java虛擬機中的原始數(shù)據(jù)類型(int,long等數(shù)值類型以及reference類型等)都不能再進一步分解,它們可以稱為標(biāo)量.
相對的,如果一個數(shù)據(jù)可以繼續(xù)分解,那它稱為聚合量.
Java中最典型的聚合量是對象.
如果逃逸分析證明一個對象不會被外部訪問,并且這個對象是可分解的,那程序真正執(zhí)行的時候?qū)⒖赡懿粍?chuàng)建這個對象,而改為直接創(chuàng)建它的若干個被這個方法使用到的成員變量來代替.
拆散后的變量便可以被單獨分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對象就無需整體分配空間了棧內(nèi)存分配
將原本分配在堆內(nèi)存上的對象轉(zhuǎn)而分配在棧內(nèi)存上,這樣就可以減少堆內(nèi)存的占用,從而減少 GC 的頻次
相關(guān)參數(shù)配置
-XX:+DoEscapeAnalysis 開啟逃逸分析(JDK1.8 中是默認(rèn)開啟)
-XX:-DoEscapeAnalysis 關(guān)閉逃逸分析
-XX:+PrintEscapeAnalysis 顯示分析結(jié)果
-XX:+EliminateLocks 開啟鎖消除(JDK1.8 中是默認(rèn)開啟)
-XX:-EliminateLocks 關(guān)閉鎖消除
-XX:+EliminateAllocations 開啟標(biāo)量替換(JDK1.8 中是默認(rèn)開啟)
-XX:-EliminateAllocations 關(guān)閉標(biāo)量替換
-XX:+PrintEliminateAllocations 顯示標(biāo)量替換詳情
逃逸實例
下面這段代碼演示了逃逸
public class EscapeAnalysisDemo {
public static Object globalVariableObject;
public Object instanceObject;
public void globalVariableEscape(){
globalVariableObject = new Object(); // 靜態(tài)變量,外部線程可見,發(fā)生逃逸
}
public void instanceObjectEscape(){
instanceObject = new Object(); // 賦值給堆中實例字段,外部線程可見,發(fā)生逃逸
}
public Object returnObjectEscape(){
return new Object(); // 返回實例,外部線程可見,發(fā)生逃逸
}
public void noEscape(){
Object noEscape = new Object(); // 僅創(chuàng)建線程可見,對象無逃逸
}
}
C1和C2的對比
- C2編譯器啟動時長比C1編譯器慢
- 系統(tǒng)穩(wěn)定執(zhí)行以后C2編譯器執(zhí)行速度遠遠快于C1編譯器
程序解釋執(zhí)行(不開啟性能監(jiān)控)可以觸發(fā)C1編譯,將字節(jié)碼編譯成機器碼,可以進行簡單優(yōu)化,也可以加上性能監(jiān)控;
C2編譯會根據(jù)性能監(jiān)控信息進行激進優(yōu)化.
不過在Java7版本之后,一旦開發(fā)人員在程序中顯式指定命令“-server"時,默認(rèn)將會開啟分層編譯策略,由C1編譯器和C2編譯器相互協(xié)作共同來執(zhí)行編譯任務(wù).
開發(fā)人員可以通過命令顯式指定Java虛擬機在運行時到底使用哪一種即時編譯器
-client
指定Java虛擬機運行在Client模式下,并使用C1編譯器纵寝;
C1編譯器會對字節(jié)碼進行簡單和可靠的優(yōu)化,耗時短,以達到更快的編譯速度
-server
指定Java虛擬機運行在Server模式下,并使用C2編譯器.
C2進行耗時較長的優(yōu)化,以及激進優(yōu)化,但優(yōu)化的代碼執(zhí)行效率更高
注意:64位操作系統(tǒng)默認(rèn)使用-server服務(wù)器模式,即C2編譯器
Graal編譯器
JDK10起,HotSpot又加入一個全新的即時編譯器: Graal編譯器
編譯效果短短幾年時間就追平了C2編譯器,未來可期.
目前,帶著“實驗狀態(tài)"標(biāo)簽,需要使用開關(guān)參數(shù)去激活,才可以使用
-XX: +UnlockExperimentalVMOptions
-XX: +UseJVMCICompiler
AOT編譯器
JDK9引入了AOT編譯器(靜態(tài)提前編譯器,Ahead Of Time Compiler)
Java 9引入了實驗性AOT編譯工具jaotc,它借助了Graal 編譯器,將所輸入的Java 類文件轉(zhuǎn)換為機器碼,并存放至生成的動態(tài)共享庫之中.
所謂AOT編譯,是與即時編譯相對立的一個概念.
我們知道,即時編譯指的是在程序的運行過程中,將字節(jié)碼轉(zhuǎn)換為可在硬件上直接運行的機器碼论寨,并部署至托管環(huán)境中的過程.
而AOT編譯指的則是,在程序運行之前,便將字節(jié)碼轉(zhuǎn)換為機器碼的過程.
最大好處:
Java虛擬機加載已經(jīng)預(yù)編譯成二進制庫,可以直接執(zhí)行.不必等待即時編譯器的預(yù)熱,減少Java應(yīng)用給人帶來“第一次運行慢”的不良體驗
缺點:
破壞了java"一次編譯,到處運行”(提前干掉了能夠跨平臺的class文件),必須為每個不同硬件、oS編譯對應(yīng)的發(fā)行包.
降低了Java鏈接過程的動態(tài)性,加載的代碼在編譯期就必須全部已知.
還需要繼續(xù)優(yōu)化中,最初只支持Linux x64 java base
請關(guān)注我的訂閱號
參考
- 《深入理解JAVA虛擬機:JVM高級特性與最佳實踐》