java虚拟机_Java 虚拟机(一)- Java 字节码

本文详细介绍了Java虚拟机(JVM)的栈基架构,包括PC寄存器、JVM栈、堆和方法区。解释了基于栈的虚拟机工作原理,以及局部变量数组和操作栈的角色。此外,还阐述了JVM的数据类型、运算指令、控制转移指令、对象创建与访问指令、操作数栈管理和方法调用返回指令等内容,为理解Java字节码执行提供了基础。
摘要由CSDN通过智能技术生成

abea3a2adb04ee53e8a4d991018c080f.png

这是我们 Java 虚拟机系列文章的第一篇, Java 字节码

Java 字节码是 JVM 里面指令的型式, Java 的源码经过 Java 编译器会形成 Java 字节码,这的字节码才能在 Java 虚拟机中运行。

fc1219fb64e420ad50e591e56c327df0.png

一、栈基架构

一个虚拟机有基于栈虚拟机(Stack based Virtual Machine)和 基于寄存器虚拟机(Register based Virtual Machine)之法, 它们的差别可以看这里。

Java 的虚拟机是基于栈的, 它包含 PC 寄存器、 JVM 栈、堆和方法区

0552ddad1a7b0d268124b40b84397c29.png

注:图片来自于 极客时间专栏《深入拆解 Java 虚拟机》

  • PC 寄存器(program counter): Java 程序里面的每一个运行的线程,都有一个 PC 寄存器存储着当前指令的地址。
  • JVM 栈:对于每一个线程,栈 是用来存放局部变量、方法参数以及返回值的
  • :所有线程共享的内存区域,存放着对象(类的实例化和数组)。对象由垃圾回收器进行再分配
  • 方法区:对于每一个加载的类,方法区里面都存放着方法的代码,以及符号表(对象或字段的引用)以及常量池中的常量

JVM 的栈是由一系列的 Frame 组成的,它包含

1b0c5de5d24fd63c11a69ab408ff082e.png

注:图片来自JVM 内部原理(六)— Java 字节码基础之一

  • 一个局部变量数组(Local Variables), 索引从 0 到数组长度减一。数据的长度由编译器计算。除了 longdouble 类型值需要两个局部变量的空间存储外,其他类型的值都可以存储在一个局部变量里;
  • 一个用来存储中间变量的操作栈(Operand Stack)。该中间变量的作用充当指令的操作数,或者存放方法调用的参数。它是一个后进先出(LIFO)栈

二、JVM 数据类型

JVM 定义的数据类型有:

  • 基本数据类型:
  • 数字类型

| 类型 | 位数| | --- | --- | |byte| 8位 | |short| 16位| |int | 32 位| |long | 64 位 | |char | 16 位| | float | 32 位 单精度| |double | 64 位 双精度|

  • boolean 类型
  • returenAdress: 指令指针,指向一条虚拟机指令的操作码
  • 引用类型
    • 类类型 class type
    • 集合类型 array type
    • 接口类型 interface type

三、指令

Java 字节码的指令有一个 JVM 数据类型前缀和操作名组成,例如 idd 指令是由 "i" 表示 int 类型数据和 “add"相加操作组成,因此 iadd 表示对 int 类型数值求和。

根据指令的性质,可以分为以下类型:

  • 加载和存储指令
  • 运算指令
  • 转移指令
  • 对象创建和访问指令
  • 操作数栈管理指令
  • 控制转移指令
  • 方法调用和返回指令
  • 异常处理指令
  • 同步指令

下面有详细的说明

1.加载和存储指令

加载和存储指令用于数据在栈帧(Frame)中的局部变量数组(Local Variables)和操作栈(Operand Stack)之间来回传输;

具体为:

  • 将一个局部变量加载到操作栈: iload, xxxload
  • 将一个数值从操作数栈存储到局部变量表: istore, xxxstore
  • 将一个常量加载到操作数栈:bipush, xxxpush, iconst_xxx, ladc_w, ladc2_w, aconst_null 等
  • 扩充局部变量表的访问索引的指令: wide

例如: 例子1: Calc.java 文件

public 

通过命令

javac Calc.java

会生成 Calc.class 文件 然后在通过命令 javap 可以看到相应的字节码

javap -v Calc.class

相应的字节码

public 

执行过程

4decc4279d351c784e19267b03d0cacc.png

3762867a85c8dd1b90b967b3d5f16d35.png

5f7c4dcd2d30fbdac2550599e5c886c3.png

注:图片来自于周志明《深入理解 Java 虚拟机》

2.运算指令

运算指令用于对两个操作栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。 分为两类:对整形数据进行运算的指令和对浮点型数据进行运算的指令。

  • 加法指令: iadd, ladd, fadd, dadd
  • 减法指令: isub, lsub, fsub, dsub
  • 乘法指令: imul, lmul, fmul, dmul
  • 除法指令: idiv, ldiv, fdiv, ddiv
  • 求余指令: irem, lrem, frem, drem
  • 取反指令: ineg, lneg, fneg, dneg
  • 位移指令: ishl, ishr, iushr, lshl, lshr, lushr
  • 按位或指令:ior, lor
  • 按位与指令: iand, land
  • 按位异或指令:ixor, lxor
  • 局部变量自增指令:iinc
  • 比较指令:dcmpg, dcmpl, fcmpg, fcmpl, lcmp

上面例子1中的执行偏移地址13指令 iadd 就是将操作栈顶中两个数 200 加上 100, 再把它的和 300 压回栈。

在除非指令(idiv, ldiv)以及求余指令(irem 和 lrem )中, 当出现除数为零时会导致虚拟机抛出 ArithmeticException 异常

3.转换指令

转换指令可以将两种不同的数值类型进行相互转换,一般用于显示类型转换 包含: i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l 和 d2f 其中 i 是 int, b 是 byte, c 是 char, s 是 short, l 是long, f 是 float, d 是 double 类型

例子2: java 源码

public 

用 javap 生成的字节码是

public 

4.对象创建与访问指令

在 Java 中一切皆为对象,类实例和数组都是对象,但是 Java 虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。

  • 创建类实例的指令: new
  • 创建数组的指令: newarray, anewarry, multianewarray
  • 访问类字段(static 字段)和实例字段(非 static 字段)的指令: getfield, putfield, getstatic, putstatic
  • 把一个数组元素加载到操作栈的指令: baload, caload, saload, iaload, laload, faload, daload, aaload
  • 把一个操作数栈的值存储到数组元素中的指令: bastore, castore, sastore, iastore, fastore, dastore, aastore
  • 取数组长度的指令 : arraylength
  • 检查类实例类型的指令: instanceof, checkcast

例3:

public 

生成的部分字节码

public 

5.操作数栈管理指令

包括

  • 将操作数栈的栈顶一个或两个元素出栈: pop, pop2
  • 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup, dup2, dup_x1, dup_x2, dup2_x1, dup2_x2;
  • 将栈最顶端的两个数值互换: swap

其中 pop 是将栈顶一个元素出栈,pop2 是将栈顶的两个元素出栈;

dup 是复制栈顶数值并将复制值压入栈顶, dup2 是复制栈顶一个(对于 long 或 double 类型)或 两个(非 long, double 类型)的数值弹出;

dup_x1 是复制栈顶的值并将两个复制值压入栈顶,dup_x2 复制栈顶一个(对于 long 或 double 类型)或 两个(非 long, double 类型)的数值并压入栈顶;

dup2_x1 是 dup_x1 的双倍版本, dup2_x2 是 dup_x2 的双倍版本;

eac539a87ff701cc4227f4f10f43f583.png

注:图片来自于JVM 内部原理(六)— Java 字节码基础之一

在 java 中 long 和 double 类型需要占用两个存储空间,为了交换两个 double 值,需要 dup2_x2

1f6624579450dc4af7ff541041523c51.png

6.控制转移指令

控制转移指令可以让 Java 虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序

  • 条件分支

| 指令 | 例子|说明 | | --- | --- | --- | |ifeq| if (i == 0)
if (bool == false) |当栈顶 int 型数值等于0 或者是 false 跳转 | |ifne| if (i != 0)
if (bool == true)|当栈顶 int 型数值不等于0 或者是 true 跳转|
|ifge| if (i >= 0)| 当栈顶 int 型数值大于或等于0时跳转| |ifgt| if (i > 0)|当栈顶 int 型数值大于0时跳转| |ifle| if (i <= 0)|当栈顶 int 型数值小于或等于0时跳转| |iflt| if (i < 0)|当栈顶 int 型数值小于0时跳转| |if_icmple||比较栈顶两个 int 型数值的大小,当结果小于或等于0时跳转|

  • 复合条件分支
    • tableswitch, 用于 switch 条件跳转, case 值连续(可变长度指令)
    • lookupswitch, 用于 switch 条件跳转, case 值不连续(可变长度指令)
  • 无条件分支
    • goto, 无条件跳转

在 Java 虚拟机中,各种类型的比较最终都会转化为 int 类型的比较。

例4:

static 

注意上面的 if_icmple 指令是比较栈顶两个 int 型数值的大小,当结果小于或等于0时跳转, 与我们代码中 a > b 刚好是相反的


例5:

static 

例6:

static 

7.方法调用和返回指令

  • invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派;
  • invokeinterface 指令用于调用接口方法,会在运行时搜索一个实现了这个接口方法的对象,找出合适的方法进行调用;
  • invokespecial 指令用于调用一些需要特效处理的实例方法,包括实例初始化方法、私有方法和父类方法
  • invokestatic 指令调用类方法(static 方法)
  • invokedynamic 指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法

8.异常处理指令

arthrow 指令是 Java 程序中显式抛出异常的操作(throw 语句)

9.同步指令

同步一段指令集序列通常是由 Java 语言中的 synchroined 语句来表示, Java 虚拟机的指令集中有 monitorenter 和 monitorexit 两条指令来支持 synchronizd.

例7:

```java static void sycn(Object object){ synchronized (object){

}
}

// 相应的字节码
static void sycn(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_STATIC
Code:
  stack=2, locals=3, args_size=1
     0: aload_0
     1: dup
     2: astore_1
     3: monitorenter
     4: aload_1
     5: monitorexit
     6: goto          14
     9: astore_2
    10: aload_1
    11: monitorexit
    12: aload_2
    13: athrow
    14: return

```

每条 monitorenter 指令都必须有其对应的 monitorexit 指令,上面例子中的 3 和 5 是对应的。9 是处理异常的,这是编译器会自动产生一个异常处理,同步指令也需要在异常的时候退出,11 monitorexit 就是编译器自动生成在异常时退出的指令。

四、参考

1.【译】如何阅读 Java 字节码 2. JVM 内部原理(六)— Java 字节码基础之一 3. 周志明《深入理解 Java 虚拟机》

公众号:流浪猫编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值