JVM指令是面向虚拟机的“机器语言”,透过JVM指令可以知道某些代码的真面目,更深入的理解一些匪夷所思的现象。下面看看几个案例:

分析案例1

JVM的指令和运行时数据区相辅相成,所有的JVM指令都能在《JVMS》规范上找到,这一章主要分析 指令结合运行时数据区。
首先看下面一个小程序,目测一下该程序的运行结果:

  1. public static void main(String[] args) {
  2. int i = 8;
  3. i = i++;
  4. System.out.println(i);
  5. }

结果是8。我一开始看到还是蒙蔽的,为啥呢?为啥呢?让我们打开它的字节码文件分析一下:

  1. 0 bipush 8
  2. 2 istore_1
  3. 3 iload_1
  4. 4 iinc 1 by 1
  5. 7 istore_1
  6. 8 getstatic #2 <java/lang/System.out>
  7. 11 iload_1
  8. 12 invokevirtual #3 <java/io/PrintStream.println>
  9. 15 return

(前面的1、2、3、4是行号,后面的0、2、3、4是字节偏移)

  1. bipush 8 ,根据官方手册,得知该条指令表示以 int 类型将操作数压入操作数栈中
  2. istore_1 表示将操作数栈顶的 int 类型的数弹出并设置给 local variable table 的索引为1的变量。 local variable table 如下所示:

image.png

  1. iload_1 表示将 local variable table 的索引为1的变量的值压入到操作数栈顶
  2. iinc 1 by 1 ,第一个 1 表示 local variable table 的索引为1的变量;第二个 1 表示要递增的步长。注意该指令对操作数栈无影响
  3. istore_1 表示将栈顶的 int 类型的值存入 local variable table 里索引为1的变量

所以最终i的值为8

分析案例2

现在有一个类:

  1. public class TestTarget {
  2. private int intValue = 100;
  3. public void sayHello(){
  4. System.out.println(intValue);
  5. }
  6. }

有一个测试程序:

  1. public final class App {
  2. public static void main(String[] args) {
  3. TestTarget testTarget = new TestTarget();
  4. testTarget.sayHello();
  5. }
  6. }

然后主要查看一下 TesetTarget 编译后的字节码文件:
image.png
TestTarget.sayHello() 方法里面的局部变量表里,第一个变量是 this ,该小程序只是为了说明为什么我们可以调用 this
这里有疑问,super 是怎么调用的呢?调用super的方法也是 invokespecial 这是为啥?
image.png

分析案例3

  1. public final class App {
  2. public static void main(String[] args) {
  3. TestTarget testTarget = new TestTarget();
  4. }
  5. }

image.png

  • new创建 TestTarget 对象,并将对象的引用放到操作数栈顶
  • dup,复制操作数栈顶的对象
  • invokespecial,调用栈顶对象的初始化方法
  • astore_1,把栈顶元素保存到 局部变量表里下标为1的变量里

说明创建一个对象至少有2步,创建对象,然后调用对象的初始化方法。 new 指令底部有这样一个描述:

The new instruction does not completely create a new instance; instance creation is not completed until an instance initialization method (§2.9) has been invoked on the uninitialized instance.

new 指令没有完整的创建一个实例,未初始化的实例直到调用了初始化方法才算完整