淺談JVM系列之JIT中的Virtual Call
Virtual Call和它的本質(zhì)
有用過(guò)PrintAssembly的朋友,可能會(huì)在反編譯的匯編代碼中發(fā)現(xiàn)有些方法調(diào)用的說(shuō)明是invokevirtual,實(shí)際上這個(gè)invokevirtual就是Virtual Call。
Virtual Call是什么呢?
面向?qū)ο蟮木幊陶Z(yǔ)言基本上都支持方法的重寫(xiě),我們考慮下面的情況:
private static class CustObj { public void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj is very good!"); } } } private static class CustObj2 extends CustObj { public final void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj2 is very good!"); } } }
我們定義了兩個(gè)類(lèi),CustObj是父類(lèi)CustObj2是子類(lèi)。然后我們通一個(gè)方法來(lái)調(diào)用他們:
public static void doWithVMethod(CustObj obj) { obj.methodCall(); }
因?yàn)閐oWithVMethod的參數(shù)類(lèi)型是CustObj,但是我們同樣也可以傳一個(gè)CustObj2對(duì)象給doWithVMethod。
怎么傳遞這個(gè)參數(shù)是在運(yùn)行時(shí)決定的,我們很難在編譯的時(shí)候判斷到底該如何執(zhí)行。
那么JVM會(huì)怎么處理這個(gè)問(wèn)題呢?
答案就是引入VMT(Virtual Method Table),這個(gè)VMT存儲(chǔ)的是該class對(duì)象中所有的Virtual Method。
然后class的實(shí)例對(duì)象保存著一個(gè)VMT的指針,執(zhí)行VMT。
程序運(yùn)行的時(shí)候首先加載實(shí)例對(duì)象,然后通過(guò)實(shí)例對(duì)象找到VMT,通過(guò)VMT再找到對(duì)應(yīng)的方法地址。
Virtual Call和classic call
Virtual Call意思是調(diào)用方法的時(shí)候需要依賴(lài)不同的實(shí)例對(duì)象。而classic call就是直接指向方法的地址,而不需要通過(guò)VMT表的轉(zhuǎn)換。
所以classic call通常會(huì)比Virtual Call要快。
那么在java中是什么情況呢?
在java中除了static, private和構(gòu)造函數(shù)之外,其他的默認(rèn)都是Virtual Call。
Virtual Call優(yōu)化單實(shí)現(xiàn)方法的例子
有些朋友可能會(huì)有疑問(wèn)了,java中其他方法默認(rèn)都是Virtual Call,那么如果只有一個(gè)方法的實(shí)現(xiàn),性能不會(huì)受影響嗎?
不用怕,JIT足夠智能,可以檢測(cè)到這種情況,在這種情況下JIT會(huì)對(duì)Virtual Call進(jìn)行優(yōu)化。
接下來(lái),我們使用JIT Watcher來(lái)進(jìn)行Assembly代碼的分析。
要運(yùn)行的代碼如下:
public class TestVirtualCall { public static void main(String[] args) throws InterruptedException { CustObj obj = new CustObj(); for (int i = 0; i < 10000; i++) { doWithVMethod(obj); } Thread.sleep(1000); } public static void doWithVMethod(CustObj obj) { obj.methodCall(); } private static class CustObj { public void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj is very good!"); } } } }
上面的例子中我們只定義了一個(gè)類(lèi)的方法實(shí)現(xiàn)。
在JIT Watcher的配置中,我們禁用inline,以免inline的結(jié)果對(duì)我們的分析進(jìn)行干擾。
如果你不想使用JIT Watcher,那么可以在運(yùn)行是添加參數(shù)-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:-Inline, 這里使用JIT Watcher是為了方便分析。
好了運(yùn)行代碼:
運(yùn)行完畢,界面直接定位到我們的JIT編譯代碼的部分,如下圖所示:
obj.methodCall相對(duì)應(yīng)的byteCode中,大家可以看到第二行就是invokevirtual,和它對(duì)應(yīng)的匯編代碼我也在最右邊標(biāo)明了。
大家可以看到在invokevirtual methodCall的最下面,已經(jīng)寫(xiě)明了optimized virtual_call,表示這個(gè)方法已經(jīng)被JIT優(yōu)化過(guò)了。
接下來(lái),我們開(kāi)啟inline選項(xiàng),再運(yùn)行一次:
大家可以看到methodCall中的System.currentTimeMillis已經(jīng)被內(nèi)聯(lián)到methodCall中了。
因?yàn)閮?nèi)聯(lián)只會(huì)發(fā)生在classic calls中,所以也側(cè)面說(shuō)明了methodCall方法已經(jīng)被優(yōu)化了。
Virtual Call優(yōu)化多實(shí)現(xiàn)方法的例子
上面我們講了一個(gè)方法的實(shí)現(xiàn),現(xiàn)在我們測(cè)試一下兩個(gè)方法的實(shí)現(xiàn):
public class TestVirtualCall2 { public static void main(String[] args) throws InterruptedException { CustObj obj = new CustObj(); CustObj2 obj2 = new CustObj2(); for (int i = 0; i < 10000; i++) { doWithVMethod(obj); doWithVMethod(obj2); } Thread.sleep(1000); } public static void doWithVMethod(CustObj obj) { obj.methodCall(); } private static class CustObj { public void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj is very good!"); } } } private static class CustObj2 extends CustObj { public final void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj2 is very good!"); } } } }
上面的例子中我們定義了兩個(gè)類(lèi)CustObj和CustObj2。
再次運(yùn)行看下結(jié)果,同樣的,我們還是禁用inline。
大家可以看到結(jié)果中,首先對(duì)兩個(gè)對(duì)象做了cmp,然后出現(xiàn)了兩個(gè)優(yōu)化過(guò)的virtual call。
這里比較的作用就是找到兩個(gè)實(shí)例對(duì)象中的方法地址,從而進(jìn)行優(yōu)化。
那么問(wèn)題來(lái)了,兩個(gè)對(duì)象可以?xún)?yōu)化,三個(gè)對(duì)象,四個(gè)對(duì)象呢?
我們選擇三個(gè)對(duì)象來(lái)進(jìn)行分析:
public class TestVirtualCall4 { public static void main(String[] args) throws InterruptedException { CustObj obj = new CustObj(); CustObj2 obj2 = new CustObj2(); CustObj3 obj3 = new CustObj3(); for (int i = 0; i < 10000; i++) { doWithVMethod(obj); doWithVMethod(obj2); doWithVMethod(obj3); } Thread.sleep(1000); } public static void doWithVMethod(CustObj obj) { obj.methodCall(); } private static class CustObj { public void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj is very good!"); } } } private static class CustObj2 extends CustObj { public final void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj2 is very good!"); } } } private static class CustObj3 extends CustObj { public final void methodCall() { if(System.currentTimeMillis()== 0){ System.out.println("CustObj3 is very good!"); } } } }
運(yùn)行代碼,結(jié)果如下:
總結(jié)
本文介紹了Virtual Call和它在java代碼中的使用,并在匯編語(yǔ)言的角度對(duì)其進(jìn)行了一定程度的分析。
以上就是淺談JVM系列之JIT中的Virtual Call的詳細(xì)內(nèi)容,更多關(guān)于JVM系列之JIT中的Virtual Call的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳細(xì)介紹idea如何設(shè)置類(lèi)頭注釋和方法注釋(圖文)
本篇文章主要介紹了idea如何設(shè)置類(lèi)頭注釋和方法注釋(圖文),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12SiteMesh如何結(jié)合Freemarker及velocity使用
這篇文章主要介紹了SiteMesh如何結(jié)合Freemarker及velocity使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10springBoot集成mybatis 轉(zhuǎn)換為 mybatis-plus方式
這篇文章主要介紹了springBoot集成mybatis 轉(zhuǎn)換為 mybatis-plus方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-1223種設(shè)計(jì)模式(12)java模版方法模式
這篇文章主要為大家詳細(xì)介紹了23種設(shè)計(jì)模式之java模版方法模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Mybatis中<if>和<choose>的區(qū)別及“=”判斷方式
這篇文章主要介紹了Mybatis中<if>和<choose>的區(qū)別及“=”判斷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Arthas在線(xiàn)java進(jìn)程診斷工具在線(xiàn)調(diào)試神器詳解
Arthas是 Alibaba 開(kāi)源的Java診斷工具,深受開(kāi)發(fā)者喜愛(ài)。這篇文章主要介紹了Arthas在線(xiàn)java進(jìn)程診斷工具 在線(xiàn)調(diào)試神器,需要的朋友可以參考下2021-11-11