JVM系列之:JIT中的Virtual Call
文章目錄
- 簡介
- Virtual Call和它的本質
- Virtual Call和classic call
- Virtual Call優化單實現方法的例子
- Virtual Call優化多實現方法的例子
- 總結
簡介
什么是Virtual Call?Virtual Call在java中的實現是怎么樣的?Virtual Call在JIT中有沒有優化?
所有的答案看完這篇文章就明白了。
Virtual Call和它的本質
有用過PrintAssembly的朋友,可能會在反編譯的匯編代碼中發現有些方法調用的說明是invokevirtual,實際上這個invokevirtual就是Virtual Call。
Virtual Call是什么呢?
面向對象的編程語言基本上都支持方法的重寫,我們考慮下面的情況:
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是子類。然后我們通一個方法來調用他們:
public static void doWithVMethod(CustObj obj){obj.methodCall();}因為doWithVMethod的參數類型是CustObj,但是我們同樣也可以傳一個CustObj2對象給doWithVMethod。
怎么傳遞這個參數是在運行時決定的,我們很難在編譯的時候判斷到底該如何執行。
那么JVM會怎么處理這個問題呢?
答案就是引入VMT(Virtual Method Table),這個VMT存儲的是該class對象中所有的Virtual Method。
然后class的實例對象保存著一個VMT的指針,執行VMT。
程序運行的時候首先加載實例對象,然后通過實例對象找到VMT,通過VMT再找到對應的方法地址。
Virtual Call和classic call
Virtual Call意思是調用方法的時候需要依賴不同的實例對象。而classic call就是直接指向方法的地址,而不需要通過VMT表的轉換。
所以classic call通常會比Virtual Call要快。
那么在java中是什么情況呢?
在java中除了static, private和構造函數之外,其他的默認都是Virtual Call。
Virtual Call優化單實現方法的例子
有些朋友可能會有疑問了,java中其他方法默認都是Virtual Call,那么如果只有一個方法的實現,性能不會受影響嗎?
不用怕,JIT足夠智能,可以檢測到這種情況,在這種情況下JIT會對Virtual Call進行優化。
接下來,我們使用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!");}}} }上面的例子中我們只定義了一個類的方法實現。
在JIT Watcher的配置中,我們禁用inline,以免inline的結果對我們的分析進行干擾。
如果你不想使用JIT Watcher,那么可以在運行是添加參數-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:-Inline, 這里使用JIT Watcher是為了方便分析。
好了運行代碼:
運行完畢,界面直接定位到我們的JIT編譯代碼的部分,如下圖所示:
obj.methodCall相對應的byteCode中,大家可以看到第二行就是invokevirtual,和它對應的匯編代碼我也在最右邊標明了。
大家可以看到在invokevirtual methodCall的最下面,已經寫明了optimized virtual_call,表示這個方法已經被JIT優化過了。
接下來,我們開啟inline選項,再運行一次:
大家可以看到methodCall中的System.currentTimeMillis已經被內聯到methodCall中了。
因為內聯只會發生在classic calls中,所以也側面說明了methodCall方法已經被優化了。
Virtual Call優化多實現方法的例子
上面我們講了一個方法的實現,現在我們測試一下兩個方法的實現:
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。
再次運行看下結果,同樣的,我們還是禁用inline。
大家可以看到結果中,首先對兩個對象做了cmp,然后出現了兩個優化過的virtual call。
這里比較的作用就是找到兩個實例對象中的方法地址,從而進行優化。
那么問題來了,兩個對象可以優化,三個對象,四個對象呢?
我們選擇三個對象來進行分析:
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!");}}} }運行代碼,結果如下:
很遺憾,代碼并沒有進行優化。
具體未進行優化的原因我也不清楚,猜想可能跟code cache的大小有關? 有知道的朋友可以告訴我。
總結
本文介紹了Virtual Call和它在java代碼中的使用,并在匯編語言的角度對其進行了一定程度的分析,有不對的地方還請大家不吝指教!
本文作者:flydean程序那些事
本文鏈接:http://www.flydean.com/jvm-virtual-call/
本文來源:flydean的博客
歡迎關注我的公眾號:程序那些事,更多精彩等著您!
總結
以上是生活随笔為你收集整理的JVM系列之:JIT中的Virtual Call的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM详解之:类的加载链接和初始化
- 下一篇: JVM系列之:JIT中的Virtual