关于java:JVM系列之JIT中的Virtual-Call

4次阅读

共计 3892 个字符,预计需要花费 10 分钟才能阅读完成。

简介

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

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

正文完
 0