一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情。
5)条件判断指令
几点说明:
- byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节
- goto 用来进行跳转到指定行号的字节码
(6)循环控制指令
比较 while 和 for 的字节码,发现它们是一模一样的
(7)构造方法
a.()V
编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方 法 ()V : ()V 方法会在类加载的初始化阶段被调用
b.()V
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构 造方法内的代码总是在最后
(8)方法调用
不同方法在调用时,对应的虚拟机指令有所区别
-
私有、构造、被final修饰的方法,在调用时都使用invokespecial指令
-
普通成员方法在调用时,使用invokespecial指令。因为编译期间无法确定该方法的内容,只有在运行期 间才能确定
-
静态方法在调用时使用invokestatic指令
-
new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
-
dup 是赋值操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配 合 invokespecial 调用该对象的构造方法 "":()V (会消耗掉栈顶一个引用),另一个要 配合 astore_1 赋值给局部变量
-
最终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静 态绑定
-
普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态
-
成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
-
比较有意思的是 d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用 invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了😂
-
还有一个执行 invokespecial 的情况是通过 super 调用父类方法
(9)多态的原理
当执行 invokevirtual 指令时,
- 先通过栈帧中的对象引用找到对象
- 分析对象头,找到对象的实际 Class
- Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
- 查表得到方法的具体地址
- 执行方法的字节码