Java字节码角度分析多态原理 ——提升硬实力8

在前面的文章中,有详细地介绍java字节码相关的知识,有兴趣的可以提前了解一下。

1.Java字节码的一段旅行经历——提升硬实力1

2.Java字节码角度分析a++ ——提升硬实力2

3.Java字节码角度分析条件判断指令 ——提升硬实力3

4.Java字节码角度分析循环控制 ——提升硬实力4

5.Java字节码角度分析判断结果 ——提升硬实力5

6.Java字节码角度分析构造方法 ——提升硬实力6

7.Java字节码角度分析方法调用 ——提升硬实力7


通过:7.Java字节码角度分析方法调用 ——提升硬实力7,我们已经知道了如下知识:

  1. invokespecial只能调用三类方法:<init>方法、private方法、super.method()。因为这三类方法的调用对象在编译时就可以确定。
  2. invokevirtual是一种动态分派的调用指令:也就是引用的类型并不能决定方法属于哪个类型。

接下来,我们将以字节码的视角来分析java多态原理,先来看一下java多态实现的示例:

// 从字节码角度来分析:多态原理
/**
 * 演示多态原理,注意加上下面的 JVM参数,禁用指针压缩
 * -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
 */
public class T12_ByteAnalyseDuoTai {
    // 此处,就会有多态应用。
    public static void test(Animal animal) {
        // 因为animal有可能是狗,也可能是猫,且不同对象eat方法实现不一样
        // 后续将演示eat方法是哪个对象调用,从字节码角度对eat方法查找调用过程
        animal.eat();
        System.out.println(animal.toString());
    }

    public static void main(String[] args) throws IOException {
        test(new Cat());
        test(new Dog());
        System.in.read();
    }
}

abstract class Animal {
    public abstract void eat();

    @Override
    public String toString() {
        return "我是" + this.getClass().getSimpleName();
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("啃骨头");
    }
}


class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
}

上述代码通过:javap -v T12_ByteAnalyseDuoTai.class进行反编译,得到如下字节码。得到:动态方法在字节码层面是通过invokevirtual 来调用的。

上述代码产生的多态应用,因为Animal具体实例有可能是狗,也可能是猫,且不同对象 eat() 方法实现不一样,接下来将演示 eat() 方法是哪个对象调用,从字节码角度对 eat() 方法查找调用过程

1、运行代码

  • 停在System.in.read() 方法上,这时运行jps获取进程id

2、运行HSDB工具

  • 进入JDK安装目录,执行:java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
  • 进入图形界面attach 进程id

3、HSDB - HotSpot Debugger -> Tools -> Find Object by Query

输入查询语句:select d from com.jvm.t07_bytecode.T12_ByteAnalyseDuoTai.Dog d,如下图

    

点击Dog 对象内存地址,就会得到Dog对象在内存的实际表示,如下图:

接下来,继续通过Dog对象内存地址继续查看Dog对象在内存的表现形式

我们复制0x000000001c794028,继续在【Tools】-> 【Inspector】查看对象在java内存的完整表示,如下:

接下来,继续查找多态方法。多态方法是存在于一张叫vtable虚方法表中,在Dog类对象在java内存的完整表示最后面。找到虚方法表,需要在当前地址 0x000000001c794028 加上 1B8 = 0x000000001c7941E0

接下来通过【Windows】 -> 【console】查找Dog类对应的虚方法表信息,指令如下:其中6是指虚方法表的长度

  • mem 0x000000001c7941E0 6

得到了Dog类对应所有重写方法的入口地址,如下图:

上述说的Dog类所有重写的方法,具体对应哪些方法呢。我们知道Dog类继承自AnimalAnimal继承自Object。接下来,分别从DogAnimalObject类进行查找。通过【Tools】 -> 【Class Browser】

说明:Dogvtable虚方法表中调用的 eat() 方法是 Dog 类的 eat() 方法,不是 Cat 类的 eat() 方法,也不是 Animal 类的 eat() 方法。

 

接下来继续虚方法表vtable其他方法调用的是哪个方法,查找Dog类的父类Animal类, 如下图:

说明:Dog类虚方法表中 toString() 方法调用的是 Animal toString() 方法,因为 Dog 类没有 toString() 方法,因父类 Animal toString() 方法。

 

最后,再来看一下Object类,如下图:

总结:通过对象找到它的 Class 类,获取到它的虚方法表后,就能确定虚方法表中每个方法实际的方法入口地址。有的来自于自己(eat方法),有的来自于父类(toString方法)。将来,对象调用方法时,就能明确知道调用哪个方法了。最后提醒大家,虚方法表是在类的加载过程的链接阶段生成的,所以在链接阶段就已经确定了虚方法表每个方法的入口地址


小结

一句话总结:invokevirtual 指令调用的对象vtable中的方法。

多态方法调用,当执行 invokevirtual指令时,

  1. 先通过栈帧中的对象引用找到对象
  2. 分析对象头,找到对象的实际Class
  3. Class结构中有vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
  4. 查表到得方法的具体地址
  5. 执行方法的字节码

多态方法调用,需要在执行过程中经过虚方法表多次查找,过程比较复杂。如果从细微的效率来说,它是不如static。

当然jvm底层也做很多虚方法表查找过程的优化,比如缓存、经常查找的方法放入缓存,这样查找较快。如果Animal只有Dog继承,没有Cat继承的话,jvm会将多态转换为单态,这样加快方法的寻址速度。

 


文章最后,给大家推荐一些受欢迎的技术博客链接

  1. Hadoop相关技术博客链接
  2. Spark 核心技术链接
  3. JAVA相关的深度技术博客链接
  4. 超全干货--Flink思维导图,花了3周左右编写、校对
  5. 深入JAVA 的JVM核心原理解决线上各种故障【附案例】
  6. 请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
  7. 聊聊RPC通信,经常被问到的一道面试题。源码+笔记,包懂

 


欢迎扫描下方的二维码或 搜索 公众号“10点进修”,我们会有更多、且及时的资料推送给您,欢迎多多交流!

                                           

       

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不埋雷的探长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值