美文网首页
【JVM】--实例分析override实现原理

【JVM】--实例分析override实现原理

作者: 小北觅 | 来源:发表于2021-01-19 21:26 被阅读0次

0x00 多态|方法重写实例

首先上例子,有Java语言基础的都能看懂,并且能够知道输出结果是什么。

package com.zhb.test;


public class OverrideTest {

    public static void main(String[] args) {
        Father son1 = new Son1();
        Father son2 = new Son2();
        son1.saySth("something");
        son2.saySth("something");
    }
}

class Father {
    public void saySth(String sth){
        System.out.println("father say :" + sth);
    }
}

class Son1 extends Father {
    public void saySth(String sth) {
        System.out.println("son1 say :" + sth);
    }
}

class Son2 extends Father {
    public void saySth(String sth) {
        System.out.println("son2 say :" + sth);
    }
}

输出结果如下:


这不难理解,这就是典型的Java的多态。多态的三要素:继承、重写、向上转型(父类引用指向子类对象)。

0x01 从字节码的角度分析

对0x00节的代码在IDEA终端(CMD或者Linux终端都可)中运行如下两条命令:
javac OverrideTest.java
javap -v -p OverrideTest 在IDEA中得到如下输出(反编译后的字节码):

警告: 二进制文件OverrideTest包含com.zhb.test.OverrideTest
Classfile /Users/zhb/IdeaProjects/test/src/main/java/com/zhb/test/OverrideTest.class
  Last modified 2021-1-19; size 465 bytes
  MD5 checksum 32fc082cff5dbe9da6bd5984a37de997
  Compiled from "OverrideTest.java"
public class com.zhb.test.OverrideTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // com/zhb/test/Son1
   #3 = Methodref          #2.#18         // com/zhb/test/Son1."<init>":()V
   #4 = Class              #20            // com/zhb/test/Son2
   #5 = Methodref          #4.#18         // com/zhb/test/Son2."<init>":()V
   #6 = String             #21            // something
   #7 = Methodref          #22.#23        // com/zhb/test/Father.saySth:(Ljava/lang/String;)V
   #8 = Class              #24            // com/zhb/test/OverrideTest
   #9 = Class              #25            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               OverrideTest.java
  #18 = NameAndType        #10:#11        // "<init>":()V
  #19 = Utf8               com/zhb/test/Son1
  #20 = Utf8               com/zhb/test/Son2
  #21 = Utf8               something
  #22 = Class              #26            // com/zhb/test/Father
  #23 = NameAndType        #27:#28        // saySth:(Ljava/lang/String;)V
  #24 = Utf8               com/zhb/test/OverrideTest
  #25 = Utf8               java/lang/Object
  #26 = Utf8               com/zhb/test/Father
  #27 = Utf8               saySth
  #28 = Utf8               (Ljava/lang/String;)V
{
  public com.zhb.test.OverrideTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class com/zhb/test/Son1
         3: dup
         4: invokespecial #3                  // Method com/zhb/test/Son1."<init>":()V
         7: astore_1
         8: new           #4                  // class com/zhb/test/Son2
        11: dup
        12: invokespecial #5                  // Method com/zhb/test/Son2."<init>":()V
        15: astore_2
        16: aload_1
        17: ldc           #6                  // String something
        19: invokevirtual #7                  // Method com/zhb/test/Father.saySth:(Ljava/lang/String;)V
        22: aload_2
        23: ldc           #6                  // String something
        25: invokevirtual #7                  // Method com/zhb/test/Father.saySth:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 7: 0
        line 8: 8
        line 9: 16
        line 10: 22
        line 11: 28
}
SourceFile: "OverrideTest.java"

我们关注main方法的反编译过后的字节码(看不懂字节码不要紧在图中大致已经标注出对应字节码的功能了,看不懂,其实也不影响继续阅读)

首先是new了Son1对象赋给son1引用,然后new了Son2对象赋给son2引用。接着把son1引用加载到操作数栈上,然后ldc这条字节码指令把常量池#6的String给push到操作数栈上,接着调用invokevirtual指令。后面的就是对son2进行同样的过程,因此略过,最后调用return指令返回。

从上面反编译的字节码中可以看到,invokevirtual后面都是#7,如下图所示:

也就是invoke指令后,都是Father.saySth的方法引用。但是为什么最后输出结果不同呢?这就涉及到invokevirtual指令的语义了。关于invokevirtual指令,准备后续专门写一篇文章专门详细分析,在这里只需要知道为什么能够准确找到相应子类的重写方法就好了。

通过查询JVM规范中的invokevirtual指令。
invokevirtual指令,Java虚拟机规范网址
知道invokevirtual会针对#7这个方法引用进行如下操作:
note:#7是resolved method

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型(本例中第一个invokevirtual指令所对应的是com/zhb/test/Son1),记作C。

  2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。

  3. 如果在C中没找到,则按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。

  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

正是因为invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的 invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,还会根据方法接收者 的实际类型来选择方法版本,这个过程就是Java语言中方法重写的本质。

参考资料

  1. https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
  2. 《深入理解Java虚拟机 第三版》--周志明

相关文章

  • 【JVM】--实例分析override实现原理

    0x00 多态|方法重写实例 首先上例子,有Java语言基础的都能看懂,并且能够知道输出结果是什么。 输出结果如下...

  • 书单

    《揭秘Java虚拟机:JVM设计原理与实现》(10分)分析 hotspot jvm 的实现(c/c++层)内存模型...

  • JVM工作原理

    JVM的生命周期## 首先分析一下JVM实例和JVM执行引擎实例的区别JVM实例:JVM实例对应了一个独立运行的j...

  • 第二章 并发机制的底层实现原理

    在JVM和CPU层面分析Java如何实现并发编程。 volatile、synchronized和原子操作的实现原理...

  • java之JVM工作原理

    JVM的生命周期 一、首先分析两个概念 JVM实例和JVM执行引擎实例(1)JVM实例对应了一个独立运行的java...

  • 如何了解JavaScript模板引擎实现原理

    这篇文章主要介绍了JavaScript模板引擎实现原理,结合实例形式详细分析了JavaScript模板引擎原理、定...

  • 手把手教你撸一个Mini JVM系列(5)之源码分析 -- 常量

    引子: 对于之前分析的Mini JVM的实现原理, 这里再加几篇关于源码的分析, 目的是为了可以更形象的展现之前所...

  • Java开发

    JVM 内存溢出实例 - 实战 JVM(二) 介绍 JVM 内存溢出产生情况分析Java - 注解详解 详细介绍 ...

  • 学习方向

    多线程,并发,锁机制,集合内部实现及原理,OOM,CPU性能分析,JVM调优,内存模型,类加载,spring,事务...

  • JavaScript链式调用实例

    这篇文章主要介绍了JavaScript链式调用,结合实例形式分析了javascript链式调用的相关原理、实现方法...

网友评论

      本文标题:【JVM】--实例分析override实现原理

      本文链接:https://www.haomeiwen.com/subject/rexczktx.html