You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java栈帧常量池引用值填充时机与方式及虚方法调用咨询

嘿,你的理解方向其实挺准的,先给你点个赞!咱们一步步拆解你问的这几个问题,把Java虚方法调用里的常量池相关细节说清楚,顺便纠正几个容易混淆的小点~

先澄清几个理解偏差

你提到“栈帧的常量池引用指向方法定义”,其实准确来说:

  • 每个类被JVM加载完成后,都会在元空间(原方法区)生成对应的运行时常量池,它属于Class对象的一部分,存储了类的常量、方法/字段的符号引用等信息。
  • 栈帧里的不是直接指向“方法定义”,而是会关联当前执行方法所属类的运行时常量池,用来在执行过程中解析符号引用、获取常量值等。
栈帧中当前常量池引用的填充时机与方式

当线程要执行某个方法时,JVM会为这个方法创建对应的栈帧并压入虚拟机栈。在栈帧初始化阶段,就会完成当前常量池引用的填充:

  1. 首先确定这个方法所属的类(比如你调用Test.main(),所属类就是Test);
  2. 这个类已经被加载完成,它的运行时常量池已经存在于元空间;
  3. JVM直接把该类运行时常量池的引用关联到新创建的栈帧中。

简单说就是:栈帧刚创建时,就绑定了它对应方法所属类的运行时常量池,这个是JVM自动完成的,不需要我们手动干预。

被调用方法的常量池引用填充时机与方式

不管是虚方法还是非虚方法,这个逻辑是一致的,只是虚方法多了一步动态分派的过程:

  1. 当执行方法调用指令(比如虚方法用invokevirtual)时,首先会通过当前栈帧关联的常量池,找到目标方法的符号引用(比如Parent.sayHello()的符号);
  2. 如果是虚方法调用,JVM会通过对象的实际类型(比如父类引用指向子类对象时,实际类型是子类)做动态分派,找到真正要执行的目标方法(比如子类重写的sayHello());
  3. 确认目标方法所属的类,如果这个类还没被加载,JVM会先完成类加载,生成它的运行时常量池;
  4. 为目标方法创建栈帧时,就把这个目标类的运行时常量池引用填充到新栈帧里。
结合虚方法调用的完整流程举个例子

看这段代码:

class Parent {
    public void sayHello() {}
}
class Child extends Parent {
    @Override
    public void sayHello() {}
}

public class Test {
    public static void main(String[] args) {
        Parent p = new Child();
        p.sayHello(); // 虚方法调用
    }
}

流程拆解:

  1. 执行main方法时,main的栈帧关联Test类的运行时常量池;
  2. 执行p.sayHello()时,通过Test的常量池找到Parent.sayHello()的符号引用;
  3. 因为是invokevirtual指令,JVM检查p指向的实际对象类型是Child,动态分派找到Child.sayHello();
  4. 如果Child类还没加载,先加载它并生成运行时常量池;
  5. 创建Child.sayHello()的栈帧,将Child类的运行时常量池引用填充到这个栈帧中;
  6. 执行Child.sayHello()的代码,执行完后栈帧弹出。

这样整个过程里,常量池引用的填充都是JVM在栈帧初始化阶段自动完成的,我们写代码时感知不到,但这是JVM执行方法的核心环节之一~

内容的提问来源于stack exchange,提问作者cloud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:39:18