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
- 一个方法的执行通常会涉及下面几部分
- java栈中,局部变量表、操作数栈
- java堆,对象的地址引用
- 常量池
- 栈数据区、方法区的设于部分
字节码指令
- 字节码对于虚拟机,属于基本执行指令
- 由一个字节长度、代表某种特定操作含义的数字,称为
操作码和后面跟随零至多个代码此操作所需参数,称为操作数组成 - 操作码的长度为一个字节 0~255,指令集的操作码总数不超过256条
- 执行模型,流程
- 自动计算PC寄存器的值+1
- 根据PC寄存器的指示位置,从字节码流中取出操作码
- if(字节码存在操作数)从字节码流中取出操作数
- 执行操作码所定义的操作
- 大多数指令包含其操作所对应的数据类型,
iload加载int到操作数栈,fload加载float到操作数栈
- 一些指令的操作数只能是指定的一个数据类型,如arraylength
- 还有一些不需要操作数,如跳转指令goto
- short、byte、boolean在字节码中转换成int处理
- 一个指令的主要功能
- 可以从局部变量表、常量池、堆中对象、方法调用、系统调用中获取数据,压入操作数栈
- 可以从操作数栈取出一些数据(pop),完成赋值、加减乘除、方法传参、系统调用等
加载与存储指令
- 用于将数据从栈帧的局部变量表和操作数之间来回传递
- 入栈代表,
load/push/ldc/const - 存储代表,
store
xload
xload/xload_<n>,将局部变量表中第n个局部变量压入操作数栈中
- x为
i/f/l/d/a,a表示对象- n为
0~3,如果有下划线的形式,表示表面上没有操作数,不需要进行去操作数的动作,操作数都隐含在指令中iload_0将局部变量表中索引为0的数据,压入操作数栈中
xconst
xconst_<n>,特定的常量入栈
- x为
i/f/l/d/a- x为i时,n为
-1~5;x为l时,n为0~1;x为f时,n为0~2;x为d时,n为0~1;x为a时,n为aconst_nulliconst_m1,将-1压入操作数栈
push
- 将入栈
- bipush 将8位整数作为参数
- sipush 将16位整数作为参数
ldc
- 接受一个8位的参数,参数指向常量池中的
int/float/String的索引 ldc_w,接受两个8位的参数ldc2_w,压入long或double类型
store
xstort/xstort_n,将操作数栈中的栈顶元素弹出,存储局部变量表的指定位置
- n表示参数存储在局部变量表的索引,
0~3
算数指令
- 作用两个操作数栈上的值进行特定运算,并将结果重新压入到操作数栈
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、doublei2l/i2f/i2dlong->float、doublel2f/l2dfloat->doublef2d- 存在精度损失,
int/long->floatlong->double
窄化类型转换
- 大范围转换成小范围,强制类型转换
int->byte、short、chari2b/i2s/i2clong->intl2ifloat->int、longf2i/f2ldouble->int、long、floatd2i/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
- 创建数组
newarray创建基本类型数组anewarray创建引用类型数组multianewarray创建多维数组
字段访问指令
- 类静态字段
getstatic压入栈putstatic保存 - 类实例字段
getfieldputfield
数组操作指令
xaload/xastore数组入栈
x为b/c/s/i/l/f/
xastort操作数栈元素存储到数组中
x为b/c/s/i/l/f/
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调用动态绑定的方法,
方法返回
- 将当前函数操作数栈栈顶元素弹出,压入函数调用者的操作数栈,当前函数操作数栈中的其他元素都会被丢弃
- 如果当前返回的是
同步synchronized方法,那么还会执行一个隐含的monitorexit指令,退出临界区
操作数栈管理指令
- 直接操作操作数栈的指令
pop/pop2弹出一个或者两个slot的数值dup/dup2/dup_x1/dup2_x1/dup_x2/dup2_x2复制湛顶的一个或两个数值,并将复制的值重新压入栈顶
_x复制栈顶数据并插入栈顶下的某个位置,插入位置为dup的系数+x的系数dup_x1,插入1+1=2,栈顶2个slot下面;dup2_x2,插入2+2=4,栈顶4个slot下面
swap将栈顶的两个slot数值交换位置nop字节码为0x00,什么都不做,一般用于调试和占位
控制转移指令
比较指令
- 数值类型的数据才能比较大小
dcmpg/dcmpl/fcmpg/fcmpl/lcmp
- d—>double;f->float;l->long
- fcmpg遇到
NaN值压入1,fcmpl压入-1
- 产生nan
float x = 0;
float y = 0 ;
float i = x / y;
条件跳转指令
- 通常和比较指令结合使用,利用比较指令进行栈顶元素的准备,然后进行条件跳转
- 弹出栈顶元素,是否满足某一条件,如果满足条件则跳转到指定位置
- 处理过程中有直接return,就没有goto了
比较跳转指令
- 将比较和跳转两个步骤合二为一,针对
int/short/byte
多条件分支跳转
- 针对
switch-case - 没有break就没有goto
- 三个以上连续的值会采用
tableswitch - lookupswitch会按照case值的大小进行排序
- 针对String的比较
- 先比较
hashcode- 再调用
equals进行比较- equals的结果为int类型,紧接着进行下一步判断,进行真正的处理
![]()
无条件跳转
goto + 两个字节的偏移量,跳转到偏移量给定的位置处goto_w + 四个字节的偏移量jsr/jsr_w/ret用于try-finally,被异常表代替,被虚拟机废弃- 循环语句过程,
判断->执行->++->判断
异常处理指令
- 异常生成过程
- 异常对象的生成过程
- throw(手动抛出/自动抛出),对应指令为
athrow,对于jvm规定的异常,在字节码上没有体现- 结果类似return,清除操作栈上的所有内容,将异常压入调用者的操作数栈
- throws,会使方法多了异常信息
- 异常处理,抓抛模型
try-catch-finally,使用异常表处理
- 异常表保留起始位置、结束位置、程序计数器记录的代表处理的偏移地址和被捕获的异常类在常量池中的索引
- 产生的异常匹配异常表中,做相应的处理
同步控制指令
- 方法级的同步,同步方法,是隐式的,字节码中没有体现,在方法访问标识中体现
- 方法内指定指令序列的同步,同步代码块
- 同步代码块中出现异常,也要及时释放锁