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

Dear 丶 2021-09-19 03:18 382阅读 0赞

f6e1bc686388bce36663d268155645ab4368e27d

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

**1. invokespecial:调用私有实例方法;

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

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

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

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

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

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

  1. package com.bolingcavalry;public class Test001 implements Action{
  2. private int add(int a, int b){
  3. return a+b;
  4. }
  5. public String getValue(int a, int b){
  6. return String.valueOf(add(a,b));
  7. }
  8. public static void output(String str){
  9. System.out.println(str);
  10. }
  11. @Override public void doAction() {
  12. System.out.println("123");
  13. }
  14. public static void main(String[] args){
  15. Test001 t = new Test001();
  16. Action a = t;
  17. String str = t.getValue(1,2);
  18. t.output(str);
  19. t.doAction();
  20. a.doAction();
  21. }
  22. public void createThread(){
  23. Runnable r = () -> System.out.println("123");
  24. }
  25. }

小结一下,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文件了,如下图:

640?tp=webp&wxfrom=5&wx\_lazy=1&wx\_co=1

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

  1. Compiled from "Test001.java"public class com.bolingcavalry.Test001 implements com.bolingcavalry.Action {
  2. public com.bolingcavalry.Test001();
  3. Code:
  4. 0: aload_0
  5. 1: invokespecial #1
  6. 4: return
  7. public java.lang.String getValue(int, int);
  8. Code:
  9. 0: aload_0
  10. 1: iload_1
  11. 2: iload_2
  12. 3: invokespecial #2
  13. 6: invokestatic #3
  14. 9: areturn
  15. public static void output(java.lang.String);
  16. Code:
  17. 0: getstatic #4
  18. 3: aload_0
  19. 4: invokevirtual #5
  20. 7: return
  21. public void doAction();
  22. Code:
  23. 0: getstatic #4
  24. 3: ldc #6
  25. 5: invokevirtual #5
  26. 8: return
  27. public static void main(java.lang.String[]);
  28. Code:
  29. 0: new #7
  30. 3: dup
  31. 4: invokespecial #8
  32. 7: astore_1
  33. 8: aload_1
  34. 9: astore_2
  35. 10: aload_1
  36. 11: iconst_1
  37. 12: iconst_2
  38. 13: invokevirtual #9
  39. 16: astore_3
  40. 17: aload_1
  41. 18: pop
  42. 19: aload_3
  43. 20: invokestatic #10
  44. 23: aload_1
  45. 24: invokevirtual #11
  46. 27: aload_2
  47. 28: invokeinterface #12, 1
  48. 33: returnpublic void createThread();
  49. Code:
  50. 0: invokedynamic #13, 0
  51. 5: astore_1
  52. 6: return}

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

invokespecial:调用私有实例方法

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

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

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

invokestatic:调用静态方法

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

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

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

invokevirtual:调用实例方法

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

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

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

invokeinterface:调用接口方法

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

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

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

invokedynamic:调用动态方法

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

  1. 0: invokedynamic #13, 0
  2. 5: astore_1
  3. 6: return
  4. 1

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

16c0f716068493496587aa96a076517a590b47f5

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

72b7a217165bbef11aed8a212d11a1e685f551ce

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,382人围观)

还没有评论,来说两句吧...

相关阅读