JVM 数据类型
Java是静态类型的,它会影响字节码指令的设计,这样指令就会期望自己对特定类型的值进行操作。例如,就会有好几个add指令用于两个数字相加:iadd、ladd、fadd、dadd。他们期望类型的操作数分别是int、long、float和double。大多数字节码都有这样的特性,它具有不同形式的相同功能,这取决于操作数类型。
JVM定义的数据类型包括
1. 基本类型
- 数值类型:byte(1个字节),short(2个字节),char(2个字节),int(4个字节),float(4个字节),long(8个字节),double(8个字节)
- 布尔类型
- 指针类型:指令类型
2. 引用类型
- 类
- 数组
- 接口
在字节码中布尔类型的支持是受限的。举例来说,没有结构能直接操作布尔值。布尔值被替换转换成 int 是通过编译器来进行的,并且最终还是被转换成 int 结构。
基于栈的架构
字节码指令集的简单性很大程度上是由于 Sun 设计了基于堆栈的 VM 架构,而不是基于寄存器架构。有各种各样的进程使用基于JVM 的内存组件, 但基本上只有 JVM 堆需要详细检查字节码指令:
PC寄存器:对于Java程序中每个正在运行的线程,都有一个PC寄存器保存着当前执行的指令地址。
堆栈:对于每个线程,都会分配一个栈,其中存放本地变量、方法参数和返回值。堆栈指针向下移动,则分配新内存;若向上移动,则释放那些内存。
堆:所有线程共享的内存和存储对象(类实例和数组)。对象回收是由垃圾收集器管理的。
方法区:对于每个已加载的类,它储存方法的代码和一个符号表(例如对字段或方法的引用)和常量池。
字节码探索
操作码 | 说明 |
---|---|
iconst_1 | 将整形变量1放入操作数栈顶 |
bipush 125 | 将byte类型的125转换成int类型压入栈 |
istore_1 | 弹出栈顶元素存入索引为1的局部变量中 |
iload_1 | 取出索引为1的局部变量中的数压入栈顶 |
iadd | 从栈顶弹出两个元素然后做加法,将结果再压入栈顶 |
- 当int取值-1~5时采用iconst指令
int取值0~5时JVM采用iconst_0、iconst_1、iconst_2、iconst_3、iconst_4、iconst_5指令将常量压入栈中,取值-1时采用iconst_m1指令将常量压入栈中。
- 当int取值-128~127时,JVM采用bipush指令将常量压入栈中。
- 当int取值-32768~32767时,JVM采用sipush指令将常量压入栈中。
- 当int取值-2147483648~2147483647时,JVM采用ldc指令将常量压入栈中。
举个栗子:
public class Text{
public static void main(String[] args){
int a = 1;
int b = 2;
int c = a + b;
}
}
打印被编译的字节码
javac Text.java //首先进行编译
javap -v Text.class //运行javap查看字节码
打印出结果很多,我们只看以下部分
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V //1
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: return
······
- descriptor:说明该方法的参数是一个字符串数组([Ljava/lang/String;),并且返回值是void(V)。
- flags:说明该方法是公开的(ACC_PUBLIC),而且是静态的(ACC_STATIC)。
从地址0到8的指令执行说明:
iconst_1:将整型变量1压入栈顶
istore_1:弹出栈顶元素存入索引为1的局部变量中
iconst_2:将整型变量2压入栈顶
istore_2:弹出栈顶元素存入索引为2的局部变量中
iload_1:取出索引为1的局部变量中的数压入栈顶
iload_2:取出索引为2的局部变量中的压入栈顶
iadd:弹出栈顶前两个元素并做加法,然后将结果压入栈顶
istore_3:弹出栈顶元素存入索引为3的局部变量中
return:从这个void方法中返回
由以上可以看出每一步指令都有JVM精确的执行。