JVM是如何实现方法调用

关键字:jvm指令

https://blog.csdn.net/zc19921215/article/details/83934539

JVM是如何识别到低该调用哪个方法的:

JVM是根据 类名+方法名+方法描述符(形参+返回类型) 来识别到底该调用哪一个方法的。

其中,重载方法的匹配优先级规则如下(Java中的重载不会根据返回类型来区分):

优先调用非自动装拆箱的
在1不行的情况下,允许自动装箱
在2也不行的情况下,允许可变长参数
 

按照绑定类型分,可以将方法分为静态绑定和动态绑定两种:

其中,绑定是指将一个方法与其所在的类/对象关联起来的方法。

静态绑定是指程序运行前就已经知道方法属于哪个类,在编译的时候就可以连接到类中定位到这个方法。在Java中,final、private、static修饰的方法以及构造方法都是属于这种类型。

动态绑定是指在程序运行过程中,更加具体的对象才能具体确定是哪个方法。(多态)

 

我们再从JVM层面分析下,JVM里面是通过哪里指令来实现方法的调用的:

invokestatic:调用静态方法
invokeinterface:调用接口方法(多态)
invokespecial:调用非静态私有方法、构造方法(包括super)
invokevirtual:调用非静态非私有方法(多态)
invokedynamic:动态调用(Java7引入的,第一次用却是在Java8中,用在了Lambda表达式和默认方法中,它允许调用任意类中的同名方法,注意是任意类,和重载重写不同)(动态 ≠ 多态)

 

 


 

那么这些指令又是怎么来调用方法的呢?(invokedynamic和这些有点不一样,稍后单独解释下)

在编译的过程中,JVM并不知道目标方法的具体内存地址,此时编译器会用”符号引用”来表示该方法(加载阶段)。当JVM进行到“解析”阶段的时候,这些引用会被替换为直接引用,这个时候就知道需要去哪里调用到方法了!

对于静态绑定的方法,直接引用就是直接指向方法的指针,而对于动态绑定的方法,直接引用其实指向方法表中的一个索引。

方法表是一个数组,每个数组元素指向一个当前类及其父类中非private的实例方法,样子如下所示:

 

 

 

由于动态绑定相比于静态绑定,在寻找方法时要出多好一个内存解析的动作,例如获取调用者类型,获取方法表,获取方法表的索引值等等,还是有点开销的,虽然这些开销是必须的。所以JVM中引入了一些优化的技术: 内存缓联+方法内联。

 

动态绑定调用优化技术:

内存缓联:

说白了就是缓存,缓存的调用者的类型已经改类型所对应的目标方法,如果以后执行时,直接拿寻找缓存中对应的方法,不然就要去方法表中查询了(类似与操作系统的pagecache)。(非动态绑定的算法是不需要缓存的)

方法内联:

任何一个方法调用,除非它被内联,否则都会有固定开销。这些开销主要是保存程序在改方法中的执行位置,以及新建、压入和弹出新方法所使用中的栈帧。对于一些非常简单的方法例如getter/setter,这部分开销耗费的时间可能超过方法本身。

方法内联就是将调用函数的表达式直接用函数的函数体来直接替换,这样就减少了寻址开销,虽然可能会增加目标程序的代码量(增加空间开销),这是一个很重要的优化方法,由JVM来实现。

 

 

 

 

独特的Invokedynamic:

前面四种指令是吧符号引用替换为直接引用,直接指到内存中的地址,也就是说他们需要指导方法所在类名,方法名以及方法描述符。但是像Lambda表达式这种,尤其是scala,只要方法描述符(又叫方法签名)对上了,可以使用引用到其他类中的方法(任意Function类中的apply)。

也就是说需要有这么一个指令,可以允许程序将调用点连接到任意符合条件的方法上,这就需要用到反射机制了,但是反射调用有反复权限检查的开销,所以JVM在底层引入了轻量级的方法句柄(MethodHandle)的概念,并由JVM对它做一些优化(如方法内联)。

可以这么理解Reflection API设计失误诶Java语言服务的,而MethodHandle则为所有运行在JVM之上的语言服务。

 

posted @ 2020-04-10 15:26  Cao_Yeung  阅读(210)  评论(0编辑  收藏  举报