本文旨在介绍java虚拟机(jvm)指令集合入门,在介绍指令集之前需要做一点准备工作:
字节码指令集的简单性很大程度上是由于 Sun 设计了基于堆栈的 VM 架构,而不是基于寄存器架构。有各种各样的进程使用基于JVM 的内存组件, 但基本上只有 JVM 堆需要详细检查字节码指令。
PC寄存器:对于Java程序中每个正在运行的每一个线程都有一个PC寄存器。在任意时刻,一条Java虚拟机线程只会执行一个犯法的代码,这个正在被执行的方法称之为当前方法。如果这个方法不是native的,那么pc寄存器就保存java虚拟机正在执行的字节码指令的地址,如果该方法是native的,那么pc急促其的值是undefined。
JVM 栈:每一个java虚拟机线程都有自己私有的Java虚拟机栈(Java Virtual Machine stack),这个栈与线程同时创建,用来存放本地变量、方法参数和返回值。下面是一个显示3个线程的堆栈示例。
Java堆:是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。Java堆在虚拟机启动的时候创建,它存储了被自动内存管理系统,也就是常说的垃圾回收器所管理的各种对象,这些受管理的对象无需也无法显式地销毁。
方法区:对于每个已加载的类,它储存了每一个类的结构信息,例如,运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容,还包括以写类,实例,接口初始化时用到的特殊方法。如下入所示:
JVM堆栈是由帧组成的,当方法被调用时,每个帧都被推到堆栈上,当方法完成时从堆栈中弹出(通过正常返回或抛出异常)。每一帧还包括:
内存结构如下图所示:
Java虚拟机的指令由一个字节长度的,代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码.
格式如下:
opcode (1 byte) operand1 (optional) operand2 (optional)
在当前执行方法的栈帧里,一条指令可以将值在操作栈中入栈或出栈,可以在本地变量数组中悄悄地加载或者存储值。
先看一个入门案例(int类型为例):
public class Hello {
public static void main(String[] args) {
int a = 1;
}
}
找到类的字节码所在的文件夹,在dos窗口执行如下命令:
javap -c Hello.class
得到如下结果:
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: return
}
我们这里关心的指令只有两个
0: iconst_1 //将一个常量加载到操作数栈
1: istore_1 //将一个数值从操作数栈存储到局部变量表
将一个常量加载到操作数栈和将一个数值从操作数栈存储到局部变量表还有其他的以写指令,这里我们通过一个案例来说明:
Java代码如下:
public class Hello {
public static void main(String[] args) {
int a20 = -2147483647;
System.out.println(a20);
int a18 = -65537;
int a17 = -65536;
int a16 = -32769;
System.out.println(a16);
int a15 = -32768;
System.out.println(a15);
int a14 = -129;
int a13 = -128;
int a12 = -2;
int a11 = -1;
int a = 0;
int a01 = 1;
int a02 = 5;
int a03 = 6;
int a04 = 127;
int a05 = 128;
int a06 = 32767;
int a07 = 32768;
int a08 = 65535;
int a09 = 65536;
int a10 = 2147483647;
System.out.println(a10);
}
}
再次执行javap -c命令,结果如下:
D:\develop\a\bin>javap -c Hello.class
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16 // int -2147483647
2: istore_1
3: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
6: iload_1
7: invokevirtual #23 // Method java/io/PrintStream.println:(I)V
10: ldc #29 // int -65537
12: istore_2
13: ldc #30 // int -65536
15: istore_3
16: ldc #31 // int -32769
18: istore 4
20: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
23: iload 4
25: invokevirtual #23 // Method java/io/PrintStream.println:(I)V
28: sipush -32768
31: istore 5
33: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
36: iload 5
38: invokevirtual #23 // Method java/io/PrintStream.println:(I)V
41: sipush -129
44: istore 6
46: bipush -128
48: istore 7
50: bipush -2
52: istore 8
54: iconst_m1
55: istore 9
57: iconst_0
58: istore 10
60: iconst_1
61: istore 11
63: iconst_5
64: istore 12
66: bipush 6
68: istore 13
70: bipush 127
72: istore 14
74: sipush 128
77: istore 15
79: sipush 32767
82: istore 16
84: ldc #32 // int 32768
86: istore 17
88: ldc #33 // int 65535
90: istore 18
92: ldc #34 // int 65536
94: istore 19
96: ldc #35 // int 2147483647
98: istore 20
100: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
103: iload 20
105: invokevirtual #23 // Method java/io/PrintStream.println:(I)V
108: return
这里制作一个思维导图来说明(这里仅仅是int类型):
public static void main(String[] args) {
boolean b1 = true;
System.out.println(b1);
boolean b2 = false;
System.out.println(b2);
}
反汇编之后的结果为:
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
5: iload_1
6: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
9: iconst_0
10: istore_2
11: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
14: iload_2
15: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
18: return
可以看出,boolean类型使用了iconst_,istore__,iload_在底层是当作int类型来处理的。