深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
27938 人已学习
课程目录
已完结 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虚拟机
登录|注册

05 | JVM是如何执行方法调用的?(下)

郑雨迪 2018-07-30
我在读博士的时候,最怕的事情就是被问有没有新的 Idea。有一次我被老板问急了,就随口说了一个。
这个 Idea 究竟是什么呢,我们知道,设计模式大量使用了虚方法来实现多态。但是虚方法的性能效率并不高,所以我就说,是否能够在此基础上写篇文章,评估每一种设计模式因为虚方法调用而造成的性能开销,并且在文章中强烈谴责一下?
当时呢,我老板教的是一门高级程序设计的课,其中有好几节课刚好在讲设计模式的各种好处。所以,我说完这个 Idea,就看到老板的神色略有不悦了,脸上写满了“小郑啊,你这是舍本逐末啊”,于是,我就连忙挽尊,说我是开玩笑的。
在这里呢,我犯的错误其实有两个。第一,我不应该因为虚方法的性能效率,而放弃良好的设计。第二,通常来说,Java 虚拟机中虚方法调用的性能开销并不大,有些时候甚至可以完全消除。第一个错误是原则上的,这里就不展开了。至于第二个错误,我们今天便来聊一聊 Java 虚拟机中虚方法调用的具体实现。
首先,我们来看一个模拟出国边检的小例子。
abstract class Passenger {
abstract void passThroughImmigration();
@Override
public String toString() { ... }
}
class ForeignerPassenger extends Passenger {
@Override
void passThroughImmigration() { /* 进外国人通道 */ }
}
class ChinesePassenger extends Passenger {
@Override
void passThroughImmigration() { /* 进中国人通道 */ }
void visitDutyFreeShops() { /* 逛免税店 */ }
}
Passenger passenger = ...
passenger.passThroughImmigration();
这里我定义了一个抽象类,叫做 Passenger,这个类中有一个名为 passThroughImmigration 的抽象方法,以及重写自 Object 类的 toString 方法。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(65)

  • 啊一大狗 置顶
    这套课很好,谢谢!
    2018-07-31
    10
  • Tony
    同提建议,代码使用英文。刚学java基础时,有老师为了便于理解用中文命名。现在都来学jvm,对java很熟悉了,看到中文不仅不会觉得通俗易懂,反而特别别扭。

    作者回复: 多谢建议!

    原本是英文的,录音的时候觉得老要切换,就给换了。。

    2018-07-30
    47
  • C_love
    提个小建议,能否在代码中都使用英文?毕竟使用中文作对象名不值得提倡

    作者回复: 谢谢建议!

    2018-07-30
    18
  • 杨军
    一直不太理解一个问题:“Java的动态类型运行期才可知”,在编译期代码写完之后应该就已经确定了吧,比如A是B的子类,“B b = new B(); b= new A()”这种情况下b的动态类型是A,Java编译器在编译阶段就可以确定啊,为什么说动态类型直到运行期才可知?
    诚心求老师解惑,这个问题对我理解Java的动态绑定机制很关键-.-
    2018-08-13
    6
    14
  • lxz
    建议结合java代码及其对应的字节码来讲解,比如常量池,方法表在字节码中对应的位置,干讲一点印象也没有
    2018-08-02
    10
  • godtrue
    1:虚方法
    方法重写的方法,可认为就是虚方法

    2:JVM怎么执行虚方法
    通过方法表,一个二维表结构,标示出类的类型、虚方法的序号。当调用虚方法的时候,先确定类型,再根据类型找方法
    2018-07-30
    10
  • J
    win10:
    java -XX:CompileCommand=dontinline,*.exit Passenger 这样是对的
    java -XX:CompileCommand=‘dontinline,*.exit’ Passenger 这样是错的
    2018-12-28
    8
  • MARK
    没用过中文写代码,居然认为中文会编译错误T﹏T
    老师是为了课件方便这样写,自己写作业就改下呗,又没规定要每个字照抄
    [root@localhost cqq]# javac Passenger.java
    [root@localhost cqq]# java Passenger
    cost time : 1167
    cost time : 3156
    [root@localhost cqq]# java -XX:CompileCommand='dontinline,*.exit' Passenger
    CompilerOracle: dontinline *.exit
    cost time : 3709
    cost time : 7557

    作者回复: 哈,我以前也认为无法编译,直到有一次我看到一个俄语的方法名。。

    另外,如果你用javap -v查看常量池的话,你会发现类名方法名以及方法描述符都是用UTF8来存的。

    2018-07-30
    8
  • 杨春鹏
    关于单态内联缓存中的记录,hotspot采用了超多态。也就是如果该调用者的动态类型不是缓存中的类型的话,直接通过基于方法表来找到具体的目标方法。那么内联缓存中的类型是永久不变,一直是第一次缓存的那个调用者类型吗?
    2018-07-31
    7
  • Rain
    一直不太理解一个问题:“Java的动态类型运行期才可知”,在编译期代码写完之后应该就已经确定了吧,比如A是B的子类,“B b = new B(); b= new A()”这种情况下b的动态类型是A,Java编译器在编译阶段就可以确定啊,为什么说动态类型直到运行期才可知?
    诚心求老师解惑,这个问题对我理解Java的动态绑定机制很关键-.-


    @杨军,我的理解是,假设C是B的另外一个子类,你的上述两句代码有可能运行在多线程环境中。假设第二行代码运行之后切换到了另外一个线程中,且b = new C()
    这个情况下,线程再切换到你的那两行代码后面的时候就不一定是A了,刘必须要在运行过程中才能确定了。
    2019-01-14
    1
    4
  • 左岸🌸开
    为什么调用超类非私有实例方法会属于静态绑定呢?

    作者回复: 通过super关键字来调用父类方法,本意就是想要调用父类的特定方法,而不是根据具体类型决定目标方法。

    2018-07-30
    1
    4
  • 加久
    任何方法调用除非被内联,否则都会有固定开销。这些开销来源于保存程序在该方法中的执行位置,以及新建、压入和...

    命中内联缓存后,不用开辟新的栈帧了??
    2019-01-31
    1
    3
  • 吾是锋子
    郑老师,您好。有个具体的问题想请教下,String类里面indexOf(String str)调用的是自己类里面indexOf(String str, int fromIndex)方法,但我自己在测试的时候却发现两个方法的速度有很明显的差异,看字节码也没有发现什么特殊。
    不知道是不是我忽略了什么,希望您能抽空点拨下,感谢!

    作者回复: HotSpot里有String.indexOf intrinsic,用了很多向量化指令,所以性能会快很多的。

    关于intrinsic的概念,你可以理解为HotSpot识别指定方法后,将其替代为语意等价的高效实现。

    2018-08-14
    3
  • 方枪枪
    一直不能明确一个问题,执行哪个方法,是不是都是在运行的时候确定的,如果是的话,coding的时候,写一个不存在的方法or传入不存在的参数,编译会报错,那这个合法性的检测,是一个什么逻辑?另外关于方法的确定,对于Java来说,是按照传入的形参确定执行哪个重写的方法,对于 groovy 是按照实际类型确定执行哪个方法,这两个区别在JVM层面是如何实现的?

    作者回复: 合法性检测是根据编译器能找到的class文件来判定的。你可以在编译后,移除掉相应的class文件或者库文件,就会出现你所说的不存在的方法的情况了。

    第二个问题,在各自的编译器中已经作出区分了。在Java字节码中就只是根据类名,方法名和方法描述符来定位方法的。

    2018-08-01
    2
  • 和风暖林
    代码用汉语也挺好的呀。来这都是学jvm的,没有来学编码规范的吧……

    作者回复: 哈,多谢支持。不过汉语编程有个问题,没办法区分大小写,因此变量名和类名容易混淆

    2018-07-30
    2
  • 寥若晨星
    我也建议老师变量名使用英文,因为习惯了,看到中文变量觉得别扭,反而增加了写入大脑缓存的时间哈哈哈
    2019-03-13
    1
  • godtrue
    3:缓存
    凡是需要提高性能的地方都需要使用,这个方法也是人类经常使用的方式,计算机中使用的也比较多,使用缓存的基本理念是,一将需要的东西提前加工好,二将加工好的东西放在获取速度更快更方便的地方

    4:内联缓存
    是JVM为了提高动态绑定或者根据动态的类类型找目标方法的一种方式,这是以空间换时间的优化思路,需要权衡利弊,视场景使用
    2018-08-01
    1
  • 礼貌
    汉语编程?

    作者回复: 哈,这个对于VM实现者来说可是feature,毕竟要存储UTF8。不过以后的代码会换到英文的。

    2018-07-30
    1
  • vimfun
    老师,打印耗时的System.out.println 用的太多了吧?

    作者回复: 你是指课后作业吗?

    打印语句每一亿次循环只会运行一次,相对来说并不耗时。

    2018-07-30
    1
  • 高家祥
    方法内联 怎么没有说呀

    作者回复: 在20 21有讲

    2019-12-02
收起评论
65
返回
顶部