深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
28017 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

04 | JVM是如何执行方法调用的?(上)

郑雨迪 2018-07-27
前不久在写代码的时候,我不小心踩到一个可变长参数的坑。你或许已经猜到了,它正是可变长参数方法的重载造成的。(注:官方文档建议避免重载可变长参数方法,见 [1] 的最后一段。)
我把踩坑的过程放在了文稿里,你可以点击查看。
void invoke(Object obj, Object... args) { ... }
void invoke(String s, Object obj, Object... args) { ... }
invoke(null, 1); // 调用第二个invoke方法
invoke(null, 1, 2); // 调用第二个invoke方法
invoke(null, new Object[]{1}); // 只有手动绕开可变长参数的语法糖,
// 才能调用第一个invoke方法
当时情况是这样子的,某个 API 定义了两个同名的重载方法。其中,第一个接收一个 Object,以及声明为 Object…的变长参数;而第二个则接收一个 String、一个 Object,以及声明为 Object…的变长参数。
这里我想调用第一个方法,传入的参数为 (null, 1)。也就是说,声明为 Object 的形式参数所对应的实际参数为 null,而变长参数则对应 1。
通常来说,之所以不提倡可变长参数方法的重载,是因为 Java 编译器可能无法决定应该调用哪个目标方法。
在这种情况下,编译器会报错,并且提示这个方法调用有二义性。然而,Java 编译器直接将我的方法调用识别为调用第二个方法,这究竟是为什么呢?
带着这个问题,我们来看一看 Java 虚拟机是怎么识别目标方法的。

重载与重写

在 Java 程序里,如果同一个类中出现多个名字相同,并且参数类型相同的方法,那么它无法通过编译。也就是说,在正常情况下,如果我们想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同。这些方法之间的关系,我们称之为重载。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(83)

  • Thomas 置顶
    看明白了......这篇真好
    2018-07-27
    23
  • 永烁星光
    写的有点晦涩难懂,看了好几遍,还是有点迷糊
    2018-07-29
    2
    140
  • 胡小榕
    请不要用中文定义类/变量,有强迫症
    2018-12-26
    44
  • godtrue
    1:方法重载
    方法名相同、方法参数类型不同(其中包括参数的个数、类型、次序,三者之中只要有一个不同就行)。以前的理解方法重载是在一个类内,今天读后感觉类间有继承关系也是存在方法重载的,需要验证一下?

    2:方法重写
    方法名相同、方法参数类型相同、方法返回值相同,类之间有继承关系,便构成方法重写。
    这个概念和之前一样,不过老师强调了父类中的方法是飞私有、非静态的,这个有待验证一下?

    3:JVM定位目标方法的关键是类名+方法名+方法参数类型+方法返回值类型,于是就出现了两种JVM找目标方法的方式,静态绑定、动态绑定

    4:静态绑定
    在解析时JVM便知道该调用那个目标方法

    5:动态绑定
    在运行时JVM需要根据对应的类类型来具体定位应该调用那个目标方法。对于方法重写,对应的类会拥有一个方法表(一个二维数组表,给方法标上序号,重写的方法序号一致)

    听了几遍,也看了几遍,感觉对具体细节还是不清楚,比如:
    1:静态绑定具体咋实现的?
    2:方法表在那里?啥时候创建的?咋和具体的类关联起来的呢?
    可能篇幅有限啊!总体老师讲的很好,有些细节没讲到,我的感觉!
    2018-07-30
    2
    32
  • jiaobuchongจุ๊บ
    参考老师最后的例子,写了博客总结了一下:https://blog.csdn.net/jiaobuchong/article/details/83722193,欢迎拍砖。

    作者回复: 赞!

    2018-11-06
    21
  • 蒙奇•D•273°
    没完全理解。有个问题,接口符号指向接口方法,但是接口是没有实现的,他的实现在其实现类里面,我理解最终应该指向接口的实现类
    2018-08-15
    2
    10
  • Askerlve
    任督二脉就靠这个系列打通了~🤑
    2018-07-28
    9
  • ...
    没完全理解,上来开头没有好的引入。
    2019-01-03
    7
  • 小贝_No_7
    如果这两个方法都是静态的,那么子类中的方法隐藏了父类中的方法。

    这句没太明白,这个(隐藏)是否有更深一层的意义?
    2018-07-28
    3
    7
  • 三木子
    需要看三遍
    2018-07-27
    5
  • Kenneth
    老师你好,课后的例子编译不通过,提示有重复的类Merchant,另外,提示找不到类VIP。可以指导一下,课后的练习题的具体操作教程吗?非常感谢🙏!
    2018-08-08
    1
    4
  • 永烁星光
    唯一的例外在于,如果虚拟机能够确定目标方法有且仅有一个,比如说目标方法被标记为 final[3][4]
    请问这里的final[3][4] 是什么意思
    2018-07-29
    4
  • 东方
    好顶赞👍
    2018-07-28
    4
  • 路阳
    符号引用转实际引用时,对于非接口符号引用的第三条,在该类及父类中没有找到目标方法,便会在其直接和间接实现的接口中查找。如果存在多个符合条件的方法,并不会随机选择一个,而是优先选择拥有最具体实现的默认方法的接口,即如果 B 继承了 A,那么 B 就比 A 更加具体。代码如下:
     interface A {
    default void hello() {
    System.out.println("Hello form A");
    }
    }
    interface B extends A {
    default void hello() {
    System.out.println("Hello from B");
    }
    }
    public class C implements A,B {
    public static void main(String[] args) {
    new C().hello(); //输出 Hello from B
    }
    }

    在 java 里如果无法判断谁更具体,则需要在 C 里面显示的覆盖 hello()方法。
    2018-10-10
    3
  • Nevermore
    由于对重载方法的区分在编译阶段已经完成,我们可以认为 Java 虚拟机不存在重载这一概念。

    为什么这么说?编译阶段和虚拟机有什么联系?
    2018-12-26
    2
    2
  • 小乙哥
    类名,方法名,参数名可以不用中午吗?
    2018-12-05
    2
  • 东方
    Merchant类中actionPrice方法返回值类型为Number
    NaiveMerchant类中actionPrice方法返回值类型为Double

    NaiveMerchant类生成的字节码中有两个参数类型相同返回值类型不同的actionPrice方法
     Method actionPrice:(DLCustomer;)Ljava/lang/Double;
     Method actionPrice:(DLCustomer;)Ljava/lang/Number; // 桥接到返回值为double的方法 flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC

     方法返回值不同为何也要产生桥接方法呢? 为了保证重写语义?

    不知为何javac在编译

    NaiveMerchant naiveMerchant = new NaiveMerchant();
    Number number = naiveMerchant.actionPrice(1d, null) // 特意要求Number类型的返回值(方法描述符)

    时,总invokevirtual到Method NaiveMerchant.actionPrice:(DLCustomer;)Ljava/lang/Double,这又是为什么呢?

    附jdk版本
    java version "1.8.0_172"
    Java(TM) SE Runtime Environment (build 1.8.0_172-b11)
    GraalVM 1.0.0-rc5 (build 25.71-b01-internal-jvmci-0.46, mixed mode)


    作者回复: 1 对的,为了保证重写语义。
    2 生成的桥接方法还有一个acc_synthetic标记,代表对程序不可见。因此javac不能直接选取那个方法。

    2018-08-11
    2
  • lxz
    接口的符号引用这部分没看懂,字节码确实是能看到invokeinterface指向了接口的方法,但是实际执行的时候,是怎么做的呢,应该执行具体实现类的字节码啊。
    另外,一个小问题,方法字节码code段内,有些类似行号的数字,在每行开头,后面跟着一个冒号,是什么意思,是行号吗?
    2018-08-02
    2
  • L.B.Q.Y
    开篇例子中的invoke(null,1)之所以选择第二个invoke(),按照重载方法选择三步骤的布骤二,不考虑变长参数但是考虑基本类型的拆装箱,正好匹配第二个invoke.
    而invoke(null,1,2)按照重载方法选择三步骤的步骤三,两个invoke方法都匹配,考虑类型的继承关系,第二个invoke更恰当。
    2018-07-27
    2
  • 杨春鹏
    老师,关于方法调用的字节码指令中的invokespecial:调用实现接口的默认方法。
    我测试了一下,发现子类中调用实现接口的默认方法还是使用的invokeinrerface。

    作者回复: 多谢指出!这里我指的是使用super关键字调用所实现接口的默认方法。

    2018-07-27
    1
    2
收起评论
83
返回
顶部