深入理解JVM方法调用的内部机制

本文深入探讨了Java中JVM如何执行方法调用,包括栈帧结构、局部变量表、操作数栈以及方法调用的字节码指令。重点讲解了动态分派的过程,特别是虚函数表在实现动态绑定中的作用,对比了invokevirtual和invokeinterface指令调用的差异,阐述了Java方法调用的高效性和灵活性。
摘要由CSDN通过智能技术生成

我们都知道,Java源代码需要编译成字节码文件,由JVM解释执行,而方法调用可以说是很常见的操作。Java不同于C++,Java中的实例方法默认是虚方法,因此父类引用调用被子类覆盖的方法时能体现多态性。下面我们来看看JVM是如何完成方法调用操作并实现动态绑定的。

栈帧结构

为了能高效地管理程序方法调用,有条不紊地进行嵌套的方法调用和方法返回,JVM维护了一个栈结构,称为虚拟机方法栈(这里没考虑Native方法)。栈里面存放的一个个实体称为栈帧,每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。在编译时,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中。

局部变量表

局部变量表用于存放方法参数和方法内部定义的局部变量。局部变量表的容量以Slot为最小单位,一个Slot可以存放一个32位以内的数据类型,long和double需要两个Slot存放。

如果执行的方法是非static方法,那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用(this)。

为了节省栈帧空间,局部变量表中的Slot是可以重用的。如果一个局部变量定义了但没有赋初值是不能使用的。

操作数栈

JVM解析执行字节码是基于栈结构的。比如做算术运算时是通过操作数栈来进行的,在调用其他方法时是通过操作数栈来进行参数的传递。

方法调用大致过程
  1. 除非被调用的方法是类方法,每一次方法调用指令之前,JVM先会把方法被调用的对象引用压入操作数栈中,除了对象的引用之外,JVM还会把方法的参数依次压入操作数栈。
  2. 在执行方法调用指令时,JVM会将函数参数和对象引用依次从操作数栈弹出,并新建一个栈帧,把对象引用和函数参数分别放入新栈帧的局部变量表slot0,1,2…。
  3. JVM把新栈帧push入虚拟机方法栈,并把PC指向函数的第一条待执行的指令。

到此,有人可能会问,JVM是如何得到被调用方法的地址呢?两种方式,一种是编译期的静态绑定,另一种是运行期的动态绑定。不同类型的方法用不同的绑定方式。

方法调用的字节码指令

JVM里面提供了4条方法调用字节码指令。分别如下:

  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器<init>方法、私有方法和父类方法(super(),super.method())
  • invokevirtual:调用所有的虚方法(静态方法、私有方法、实例构造器、父类方法、final方法都是非虚方法)
  • invokeinterface:调用接口方法,会在运行时期再确定一个实现此接口的对象

invokestatic和invokespecial指令调用的方法都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载阶段就会把符号引用解析为该方法的直接引用。直接引用就是一个指针或偏移量,可以让JVM快速定位到具体要调用的方法。

invokevirtual和invokeinterface指令调用的方法是在运行时确定具体的方法地址,接口方法和实例对象公有方法可以用这两个指令来调用。

下面我们通过一个代码示例来展现这几种方法调用:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值