Java-第十四部分-JVM-Javap指令和字节码指令

556 阅读10分钟

JVM全文

Javap指令

  • 反解析工具
  • javac -g xx.java生成的class文件,包括局部变量表信息

idea默认生成局部变量表

  • javap <options> <classes>
  • 详细参数
--help -help -h -?               输出此帮助消息
-version                         版本信息,指令的jdk
-v  -verbose                     输出附加信息,比-c更加详细,包括类信息、常量池信息等
-l                               输出行号和本地变量表
-public                          仅显示公共类和成员
-protected                       显示受保护的/公共类和成员
-package                         显示程序包/受保护的/公共类和成员 (默认)
-p  -private                     显示所有类和成员
-c                               对代码进行反汇编,主要是指令信息
-s                               输出内部类型签名
-sysinfo                         显示正在处理的类的系统信息(路径、大小、日期、SHA-256 散列)
-constants                       显示最终常量
--module <模块>, -m <模块>        指定包含要反汇编的类的模块
-J<vm-option>                    指定 VM 选项
--module-path <路径>             指定查找应用程序模块的位置
--system <jdk>                   指定查找系统模块的位置
--class-path <路径>              指定查找用户类文件的位置
-classpath <路径>                指定查找用户类文件的位置
-cp <路径>                       指定查找用户类文件的位置
-bootclasspath <路径>            覆盖引导类文件的位置
--multi-release <version>        指定要在多发行版 JAR 文件中使用的版本
  • javap编译后的内容多了一个args_size=0

静态方法没有参数则为0;成员方法没有参数,默认为1,有this

  • 一个方法的执行通常会涉及下面几部分
  1. java栈中,局部变量表、操作数栈
  2. java堆,对象的地址引用
  3. 常量池
  4. 栈数据区、方法区的设于部分

字节码指令

  • 字节码对于虚拟机,属于基本执行指令
  • 由一个字节长度、代表某种特定操作含义的数字,称为操作码和后面跟随零至多个代码此操作所需参数,称为操作数组成
  • 操作码的长度为一个字节 0~255,指令集的操作码总数不超过256条
  • 执行模型,流程
  1. 自动计算PC寄存器的值+1
  2. 根据PC寄存器的指示位置,从字节码流中取出操作码
  3. if(字节码存在操作数)从字节码流中取出操作数
  4. 执行操作码所定义的操作
  • 大多数指令包含其操作所对应的数据类型,iload加载int到操作数栈,fload加载float到操作数栈
  1. 一些指令的操作数只能是指定的一个数据类型,如arraylength
  2. 还有一些不需要操作数,如跳转指令goto
  • short、byte、boolean在字节码中转换成int处理
  • 一个指令的主要功能
  1. 可以从局部变量表、常量池、堆中对象、方法调用、系统调用中获取数据,压入操作数栈
  2. 可以从操作数栈取出一些数据(pop),完成赋值、加减乘除、方法传参、系统调用等

加载与存储指令

  • 用于将数据从栈帧的局部变量表和操作数之间来回传递
  • 入栈代表,load/push/ldc/const
  • 存储代表,store

xload

  • xload/xload_<n>,将局部变量表中第n个局部变量压入操作数栈中
  1. x为i/f/l/d/a,a表示对象
  2. n为0~3,如果有下划线的形式,表示表面上没有操作数,不需要进行去操作数的动作,操作数都隐含在指令中
  3. iload_0将局部变量表中索引为0的数据,压入操作数栈中

xconst

  • xconst_<n>,特定的常量入栈
  1. x为i/f/l/d/a
  2. x为i时,n为-1~5;x为l时,n为0~1;x为f时,n为0~2;x为d时,n为0~1;x为a时,n为aconst_null
  3. iconst_m1,将-1压入操作数栈

push

  • 将入栈
  1. bipush 将8位整数作为参数
  2. sipush 将16位整数作为参数

ldc

  • 接受一个8位的参数,参数指向常量池中的int/float/String的索引
  • ldc_w,接受两个8位的参数
  • ldc2_w,压入long或double类型 image.png

store

  • xstort/xstort_n,将操作数栈中的栈顶元素弹出,存储局部变量表的指定位置
  1. n表示参数存储在局部变量表的索引,0~3

算数指令

  • 作用两个操作数栈上的值进行特定运算,并将结果重新压入到操作数栈 image.png

add的细节

i = i + 10;
3 iload_1
4 bipush 10
6 iadd
7 istore_1
  • 自增
i += 10;
8 iinc 1 by 10
  • ++的细节,不涉及其他的运算,完全一样
int i = 10;
i++;
++i;
// 对应字节码
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 iinc 1 by 1
9 return
  • 涉及与其他变量的运算
int i = 10;
// 对应字节码
0 bipush 10
2 istore_1

int m = i++;
// 对应字节码,先加载,再让局部变量表中的i++,再保存
// 如果此时 i = i++,实际上i的值不变
3 iload_1
4 iinc 1 by 1
7 istore_2

int n = ++i;
// 对应字节码,先局部变量表中的i++,再加载,再保存
8 iinc 1 by 1
11 iload_1
12 istore_3

类型转换指令

  • 将两种不同的数值类型进行相互转换
  • 实现用户代码中的显式类型转换操作,处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题
  • byte、short、boolean、char在字节码中,以int来处理,槽最小就是32位

宽化类型转换

  • 小范围转换成大范围
  • int->long、float、double i2l/i2f/i2d
  • long->float、double l2f/l2d
  • float->double f2d
  • 存在精度损失,int/long->float long->double

窄化类型转换

  • 大范围转换成小范围,强制类型转换
  • int->byte、short、char i2b/i2s/i2c
  • long->int l2i
  • float->int、long f2i/f2l
  • double->int、long、float d2i/d2l/d2f
  • 精度损失,导致转换的结果具备不同的正负号、不同的数量级
  • long->byte,需要两条指令支持
long l = 10l;
byte b = (byte) l;
// 对应的字节码指令
15 ldc2_w #2 <10>
18 lstore 6
20 lload 6
22 l2i
23 i2b
24 istore 8
  • short->byte,jvm将short看作int处理,直接调用i2b

对象的创建与访问指令

创建指令

  • 创建实例类和数组
  • 创建类实例 new + 操作数 指向常量池的索引,表示要创建的类型,完成后将对象压入栈
Object o = new Object();
File file = new File("312312");

0 new #2 <java/lang/Object> //压入栈
3 dup //复制一份
4 invokespecial #1 <java/lang/Object.<init> : ()V> //复制的一份调用init方法
7 astore_1 //压入一个
8 new #3 <java/io/File>
11 dup
12 ldc #4 <312312>
14 invokespecial #5 <java/io/File.<init> : (Ljava/lang/String;)V>
17 astore_2
18 return
  • 创建数组
  1. newarray 创建基本类型数组
  2. anewarray 创建引用类型数组
  3. multianewarray 创建多维数组

字段访问指令

  • 类静态字段 getstatic压入栈 putstatic保存
  • 类实例字段 getfield putfield

数组操作指令

  • xaload/xastore 数组入栈

xb/c/s/i/l/f/

  • xastort 操作数栈元素存储到数组中

xb/c/s/i/l/f/ image.png

int[] ints = new int[10];
ints[3] = 20;
System.out.println(ints[3]);

0 bipush 10 //数组大小 
2 newarray 10 (int) //生成数组
4 astore_1 //存储数组引用
//数组存储
5 aload_1 //加载数组引用
6 iconst_3 //加载数组索引
7 bipush 20 //加载要保存的值
9 iastore //弹出上面三个,进行处理
10 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
//数组取出
13 aload_1 //加载数组引用
14 iconst_3 //加载索引
15 iaload //根据引用索引找到对应的值,并压入栈中
16 invokevirtual #3 <java/io/PrintStream.println : (I)V>
19 return
  • arrlength 获取数组长度
int[] ints = new int[10];
int length = ints.length;

0 bipush 10
2 newarray 10 (int)
4 astore_1
//压入数组引用
5 aload_1 
6 arraylength
7 istore_2
8 return

类型检查指令

  • instanceof用来判断对象是否是某一个类的实例,将结果压入操作数栈
  • checkcast 检查类型强制转换是否可以进行,如果可以不改变操作数栈,否则抛出ClassCastException异常

方法调用与返回

方法调用

  • invokevirtual 调用对象实例方法,根据对象的实际类型进行分派,支持多态
  • invokeinterface 调用接口方法,搜索特定对象所实现的这个接口方法
  • invokespecial 调用需要特殊处理的实例方法,实例初始化方法(构造器)、私有方法和父类方法,都是静态类型绑定的,静态分派,不会进行动态派发,不存在方法重写的方法
  • invokestatic 调用命名类中的类方法,静态绑定,静态分派,优先级最高,只要是静态方法,都是这个
  • invokedynamic 调用动态绑定的方法,

方法返回

  • 将当前函数操作数栈栈顶元素弹出,压入函数调用者的操作数栈,当前函数操作数栈中的其他元素都会被丢弃 IMG_1A37274A84EA-1.jpeg
  • 如果当前返回的是同步synchronized方法,那么还会执行一个隐含的monitorexit指令,退出临界区

操作数栈管理指令

  • 直接操作操作数栈的指令
  • pop/pop2 弹出一个或者两个slot的数值
  • dup/dup2/dup_x1/dup2_x1/dup_x2/dup2_x2 复制湛顶的一个或两个数值,并将复制的值重新压入栈顶
  1. _x复制栈顶数据并插入栈顶下的某个位置,插入位置为dup的系数+x的系数
  2. dup_x1,插入1+1=2,栈顶2个slot下面;dup2_x2,插入2+2=4,栈顶4个slot下面
  • swap 将栈顶的两个slot数值交换位置
  • nop 字节码为0x00,什么都不做,一般用于调试和占位

控制转移指令

比较指令

  • 数值类型的数据才能比较大小
  • dcmpg/dcmpl/fcmpg/fcmpl/lcmp
  1. d—>double;f->float;l->long
  2. fcmpg遇到NaN值压入1,fcmpl压入-1
  • 产生nan
float x = 0;
float y = 0 ;
float i = x / y;

条件跳转指令

  • 通常和比较指令结合使用,利用比较指令进行栈顶元素的准备,然后进行条件跳转 image.png
  • 弹出栈顶元素,是否满足某一条件,如果满足条件则跳转到指定位置 IMG_C860CE27DC05-1.jpeg
  • 处理过程中有直接return,就没有goto了 image.png

比较跳转指令

  • 将比较和跳转两个步骤合二为一,针对int/short/byte IMG_847594CA0141-1.jpeg

多条件分支跳转

  • 针对switch-case IMG_97E7D15BC043-1.jpeg
  • 没有break就没有goto image.png
  • 三个以上连续的值会采用tableswitch image.png
  • lookupswitch会按照case值的大小进行排序 image.png
  • 针对String的比较
  1. 先比较hashcode
  2. 再调用equals进行比较
  3. equals的结果为int类型,紧接着进行下一步判断,进行真正的处理 image.png image.png

无条件跳转

  • goto + 两个字节的偏移量,跳转到偏移量给定的位置处
  • goto_w + 四个字节的偏移量
  • jsr/jsr_w/ret用于try-finally,被异常表代替,被虚拟机废弃 IMG_49D5B47D15EC-1.jpeg
  • 循环语句过程,判断->执行->++->判断 image.png

异常处理指令

  • 异常生成过程
  1. 异常对象的生成过程
  2. throw(手动抛出/自动抛出),对应指令为athrow,对于jvm规定的异常,在字节码上没有体现
  3. 结果类似return,清除操作栈上的所有内容,将异常压入调用者的操作数栈 image.png
  • throws,会使方法多了异常信息 image.png
  • 异常处理,抓抛模型try-catch-finally,使用异常表处理
  1. 异常表保留起始位置、结束位置、程序计数器记录的代表处理的偏移地址和被捕获的异常类在常量池中的索引
  • 产生的异常匹配异常表中,做相应的处理 image.png

同步控制指令

  • 方法级的同步,同步方法,是隐式的,字节码中没有体现,在方法访问标识中体现 image.png
  • 方法内指定指令序列的同步,同步代码块 image.png
  • 同步代码块中出现异常,也要及时释放锁 image.png