解释执行

image.png最下面的那条分支就是传统编译原理中程序代码到目标机器代码的生成过程,而中间的那条分支,自然就是解释执行的过程。

基于Java虚拟机、物理机等的语言大多会遵循这种基于现代经典编译原理的思路,在执行前先对程序源码进行词法分析和语法分析处理,把源码转化为抽象语法树(AST)。

Javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。

基于栈的解释器的执行过程

  1. public int calc() {
  2. int a = 100;
  3. int b = 200;
  4. int c = 300;
  5. return (a + b) * c;
  6. }

以上面的代码为例,看看虚拟机是如何执行的。使用javap命令查看它的字节码指令,字节码指令如下:

  1. public int calc();
  2. descriptor: ()I
  3. flags: ACC_PUBLIC
  4. Code:
  5. stack=2, locals=4, args_size=1
  6. 0: bipush 100
  7. 2: istore_1
  8. 3: sipush 200
  9. 6: istore_2
  10. 7: sipush 300
  11. 10: istore_3
  12. 11: iload_1
  13. 12: iload_2
  14. 13: iadd
  15. 14: iload_3
  16. 15: imul
  17. 16: ireturn
  18. LineNumberTable:
  19. line 3: 0
  20. line 4: 3
  21. line 5: 7
  22. line 6: 11
  23. }

编译后的字节码指令显示这段代码需要深度为2的操作数栈和4个Slot的局部变量空间。我们通过下面几张图来了解代码执行过程中的代码、操作数栈和局部变量表的变化情况。
image.png

执行偏移地址为0的指令情况

  1. 首先执行偏移地址为0的指令,bipush指令的作用是将单个字节的整形常量值(-128~127)推入操作栈顶,跟随有一个参数,指明推送的常量值,这里是100。
  2. 执行偏移地址为2的指令,istore_1指令的作用是将操作栈顶的整形值出栈并存入局部变量表Slot中。后续4条指令都是做一样的事情,也就是在对应代码中把变量a、b、c赋值为100、200、300。
  3. 执行偏移地址为11的指令,iload_1指令的作用是将局部变量表第一个Slot中的整形值复制到操作栈顶。
  4. 执行偏移地址为12的指令,iload_2指令的执行过程与iload_1类似,把第2个Slot的整形值入栈。当前局部变量表和操作栈如下图所属:

image.png

  1. 执行偏移地址为13的指令,iadd指令的作用是将操作数栈中头两个栈顶元素出栈,做整形加法,然后把结果重新入栈。在iadd指令执行完毕后,栈中原有的100和200出栈,它们的和300重新入栈。
  2. 执行偏移地址为14的指令,iload_3指令把存放在第3个局部变量Slot中的300压入操作栈中。这时操作栈中为两个整数300。下一条指令imul是将操作栈中头两个栈顶元素出栈,做整形乘法,然后把结果重新入栈,与iadd完全类似。
  3. 执行偏移地址16的指令,ireturn指令是方法返回指令之一,它将结束方法执行并将操作栈顶的整形值返回给此方法的调用者。到此为止,这段代码执行结束。

    参考文章: https://www.yuque.com/books/share/9a0dfcb7-f745-4615-b3cc-c87a888af356/rt0qs2#OOCnY https://www.jianshu.com/p/20c2b6f5fe59