简介

什么是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的博客

欢送关注我的公众号:程序那些事,更多精彩等着您!