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