Java虚拟机学习:方法调用的字节码指令

我们在写java程序的时候会进行各种方法调用,虚拟机在执行这些调用的时候会用到不同的字节码指令,共有如下五种: 

1. invokespecial:调用私有实例方法; 2. invokestatic:调用静态方法; 3. invokevirtual:调用实例方法; 4. invokeinterface:调用接口方法; 5.  invokedynamic:调用动态方法;

这里我们通过一个实例将这些方法调用的字节码指令逐个列出。

说到这里,也给大家推荐一个架构交流学习群:835544715,里面会分享一些资深架构师录制的视频录像:Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。

实例共两个java文件,一个是接口另一个是类,先看接口源码,很简单只有一个方法声明:

package com.bolingcavalry;public interface Action {
   void doAction();
}

接下来的类实现了这个接口,而且还有自己的共有、私有、静态方法:

package com.bolingcavalry;public class Test001 implements Action{
   private int add(int a, int b){
       return a+b;
   }

   public String getValue(int a, int b){
       return String.valueOf(add(a,b));
   }

   public static void output(String str){
       System.out.println(str);
   }

   @Override
   public void doAction() {
       System.out.println("123");
   }

   public static void main(String[] args){
       Test001 t = new Test001();
       Action a = t;
       String str = t.getValue(1,2);
       t.output(str);
       t.doAction();
       a.doAction();
   }

   public void createThread(){
       Runnable r = () -> System.out.println("123");
   }
}

小结一下,Test001的代码中主要的方法如下: 1. 一个私有方法add; 2. 一个公有方法getValue,里面调用了add方法; 3. 一个静态方法output; 4. 实现接口定义的doAction; 5. 一个公有方法,里面使用了lambda表达式; 6. main方法中,创建对象,调用getValue,output,doAction;

接下来我们通过javac命令或者ide工具得到Action.class和Test001.class文件,如果是用intellij idea,可以先把Test001运行一遍,然后在工程目录下找到out文件夹,打开后里面是production文件夹,再进去就能找到对应的package和class文件了,如下图:

打开命令行,在Test001.class目录下执行javap -c Test001.class ,就可以对class文件进行反汇编,得到结果如下:

Compiled from "Test001.java"public class com.bolingcavalry.Test001 implements com.bolingcavalry.Action {
  public com.bolingcavalry.Test001();
   Code:
      0: aload_0
      1: invokespecial #1                  
      4: return

 public java.lang.String getValue(int, int);
   Code:
      0: aload_0
      1: iload_1
      2: iload_2
      3: invokespecial #2                  
      6: invokestatic  #3                  
      9: areturn

 public static void output(java.lang.String);
   Code:
      0: getstatic     #4                  
      3: aload_0
      4: invokevirtual #5                  
      7: return

 public void doAction();
   Code:
      0: getstatic     #4                  
      3: ldc           #6                  
      5: invokevirtual #5                  
      8: return

 public static void main(java.lang.String[]);
   Code:
      0: new           #7                  
      3: dup
      4: invokespecial #8                  
      7: astore_1
      8: aload_1
      9: astore_2
     10: aload_1
     11: iconst_1
     12: iconst_2
     13: invokevirtual #9                  
     16: astore_3
     17: aload_1
     18: pop
     19: aload_3
     20: invokestatic  #10                 
     23: aload_1
     24: invokevirtual #11                 
     27: aload_2
     28: invokeinterface #12,  1           
     33: returnpublic void createThread();
   Code:
      0: invokedynamic #13,  0             
      5: astore_1
      6: return}

现在我们可以对比反汇编结果来学习字节码的用法了:

invokespecial:调用私有实例方法

getValue()方法中调用了私有实例方法add(int a, int b),反编译结果如下所示,注意编号为3的那一行:

public java.lang.String getValue(int, int);
   Code:
      0: aload_0
      1: iload_1
      2: iload_2
      3: invokespecial #2                  
      6: invokestatic  #3                  
      9: areturn

可见私有实例方法的调用是通过invokespecial指令来实现的;

invokestatic:调用静态方法

getValue()方法中,调用了静态方法String.valueOf(),反编译结果如下所示,注意编号为6的那一行:

public java.lang.String getValue(int, int);
   Code:
      0: aload_0
      1: iload_1
      2: iload_2
      3: invokespecial #2                  
      6: invokestatic  #3                  
      9: areturn

可见静态方法的调用是通过invokestatic指令来实现的;

invokevirtual:调用实例方法

在main()方法中,调用了t.getValue(1,2)方法,反编译结果如下所示,注意编号为13的那一行:

public static void main(java.lang.String[]);
   Code:
      0: new           #7                  
      3: dup
      4: invokespecial #8                  
      7: astore_1
      8: aload_1
      9: astore_2
     10: aload_1
     11: iconst_1
     12: iconst_2
     13: invokevirtual #9                  
     16: astore_3
     17: aload_1
     18: pop
     19: aload_3
     20: invokestatic  #10                 
     23: aload_1
     24: invokevirtual #11                 
     27: aload_2
     28: invokeinterface #12,  1           
     33: return}

可见调用一个实例的方法的时候,通过invokevirtual指令来实现的;

invokeinterface:调用接口方法

在main()方法中,我们声明了接口Action a,然后调用了a.doAction(),反编译结果如下所示,注意编号为28的那一行:

public static void main(java.lang.String[]);
   Code:
      0: new           #7                  
      3: dup
      4: invokespecial #8                  
      7: astore_1
      8: aload_1
      9: astore_2
     10: aload_1
     11: iconst_1
     12: iconst_2
     13: invokevirtual #9                  
     16: astore_3
     17: aload_1
     18: pop
     19: aload_3
     20: invokestatic  #10                 
     23: aload_1
     24: invokevirtual #11                 
     27: aload_2
     28: invokeinterface #12,  1           
     33: return}

可见调用一个接口的方法是通过invokeinterface指令来实现的; 其实t.doAction()和a.doAction()最终都是调用Test001的实例的doAction,但是t的声明是类,a的声明是接口,所以两者的调用指令是不同的;

invokedynamic:调用动态方法

在main()方法中,我们声明了一个lambda() -> System.out.println(“123”),反编译的结果如下:

0: invokedynamic #13,  0             
      5: astore_1
      6: return
1

可见lambda表达式对应的实际上是一个invokedynamic调用,具体的调用内容,可以用Bytecode viewer这个工具来打开Test001.class再研究,由于反编译后得到invokedynamic的操作数是#13,我们先去常量池看看13对应的内容:

是个Name and type和Bootstrap method,再细看Bootstrap method的操作数,如下图:

是个MethodHandler的引用,指向了用户实现的lambda方法;

以上就是五种方法调用的字节码指令的简单介绍,实际上每个指令背后都对应着更复杂的调用和操作,有兴趣的读者可以通过虚拟机相关的书籍和资料继续深入学习。

想要学习Java高架构、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频免费获取 架构群:835544715

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA高级架构开发

Java虚拟机学习:方法调用的字节码指令

我们在写java程序的时候会进行各种方法调用,虚拟机在执行这些调用的时候会用到不同的字节码指令,共有如下五种:

1200
来自专栏数据结构与算法

LOJ#515. 「LibreOJ β Round #2」贪心只能过样例(bitset)

一共有 nnn个数,第 iii 个数 xix_ix​i​​ 可以取 [ai,bi][a_i , b_i][a​i​​,b​i​​] 中任意值。 设 S=∑xi2...

1193
来自专栏三丰SanFeng

Linux64位程序移植

1 概述 Linux下的程序大多充当服务器的角色,在这种情况下,随着负载量和功能的增加,服务器所使用内存必然也随之增加,然而32位系统固有的4GB虚拟地址空间限...

2697
来自专栏安恒网络空间安全讲武堂

​CTF逆向——常规逆向篇(下)

CTF逆向——常规逆向篇(下) 题目: CrackMe.exe(NSCTF reverse第一题) WHCTF2017 reverse HCTF reverse...

5065
来自专栏肖洒的博客

Java面试笔记

Java最重要的特点就平台独立,平台独立意味着可以在一个系统编译它然后在另一个系统使用它。

902
来自专栏算法channel

面试被问到动态内存分配时需要注意哪些坑,该怎么回答?

面试时,面试官问我们Java,Python这种语言那是必须要准确回答的,很多系统如果对性能要求高的话,底层一般会用到C/C++语言,因此被问到底层语言的相关知识...

1443
来自专栏hbbliyong

C++为啥要使用new

1.为什么要有new? 为什么要有new?为什么要动态创建对象?为什么有时候不用new,有时候又用new,比如: // Cocos2d-x3.x的Value类,...

42312
来自专栏AI研习社

嘀~正则表达式快速上手指南(下篇)

上面的代码中用 for 循环去遍历 contents 这样我们就可以一个一个处理每封邮件。我们创建一个字典, emails_dict,这将保存每个电子邮件的所有...

691
来自专栏技术记录

通讯协议序列化解读(一) Protobuf详解教程

前言:说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性。但是在越来越多的应用场景里,JSON冗长的缺点导致...

984
来自专栏HappenLee的技术杂谈

编码与模式------《Designing Data-Intensive Applications》读书笔记5

1、在内存中,数据是保存在对象、结构、列表、数组、哈希表、树、等等。这些数据结构在内存之中被优化为CPU可以高效访问和操作的结构(通常这是操作系统的任务,并不需...

1044

扫码关注云+社区

领取腾讯云代金券