简介
什么是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的博客
欢送关注我的公众号:程序那些事,更多精彩等着您!