JVM指令是面向虚拟机的“机器语言”,透过JVM指令可以知道某些代码的真面目,更深入的理解一些匪夷所思的现象。下面看看几个案例:
分析案例1
JVM的指令和运行时数据区相辅相成,所有的JVM指令都能在《JVMS》规范上找到,这一章主要分析 指令结合运行时数据区。
首先看下面一个小程序,目测一下该程序的运行结果:
public static void main(String[] args) {
int i = 8;
i = i++;
System.out.println(i);
}
结果是8。我一开始看到还是蒙蔽的,为啥呢?为啥呢?让我们打开它的字节码文件分析一下:
0 bipush 8
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return
(前面的1、2、3、4是行号,后面的0、2、3、4是字节偏移)
bipush 8
,根据官方手册,得知该条指令表示以int
类型将操作数压入操作数栈中istore_1
表示将操作数栈顶的int
类型的数弹出并设置给local variable table
的索引为1的变量。local variable table
如下所示:
iload_1
表示将local variable table
的索引为1的变量的值压入到操作数栈顶iinc 1 by 1
,第一个1
表示local variable table
的索引为1的变量;第二个1
表示要递增的步长。注意该指令对操作数栈无影响。istore_1
表示将栈顶的int
类型的值存入local variable table
里索引为1的变量
所以最终i的值为8
分析案例2
现在有一个类:
public class TestTarget {
private int intValue = 100;
public void sayHello(){
System.out.println(intValue);
}
}
有一个测试程序:
public final class App {
public static void main(String[] args) {
TestTarget testTarget = new TestTarget();
testTarget.sayHello();
}
}
然后主要查看一下 TesetTarget
编译后的字节码文件:TestTarget.sayHello()
方法里面的局部变量表里,第一个变量是 this
,该小程序只是为了说明为什么我们可以调用 this
。
这里有疑问,super 是怎么调用的呢?调用super的方法也是 invokespecial
这是为啥?
分析案例3
public final class App {
public static void main(String[] args) {
TestTarget testTarget = new TestTarget();
}
}
- 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
指令没有完整的创建一个实例,未初始化的实例直到调用了初始化方法才算完整