JVM基本操作指令
一. 概要
JVM操作指令是字节码插桩必须要掌握的知识点,后面我们操作字节码,几乎每一行代码都离不开我们对操作指令的运用。
本篇将介绍常用的JVM操作指令,或者我们后期实际Demo中,我们会用到的指令。
二. 操作指令
同类型的指令,可以针对不同数据类型,我们定义了一个变量x,x可以是以下变量:
- a: 对象类型
- i: int
- l: long
- f: float
- d: double
- b: byte
- c: char
- s: short
- 有些时候没有针对于byte和short的专用字节码,因为在JVM中,byte和short在被计算时会被强制拉长为int,所以它们使用的和int一样。
- char和int能互相转换,boolean类似,它们也需要使用int的字节码,而且boolean值的false就是int值0,而true就是int值1。
- 在局部变量表中,32位以内的类型只占有一个slot(包括引用数据类型),64位的类型(long和double)占有两个slot,后面Demo会举例。
1. xload n
从局部变量表取出下标为n的元素压入到操作数栈 栈顶
iload 1
2. xstore n
从操作数栈 栈顶弹出 放入局部变量表下标为n的位置
istore 1
3. xreturn
返回操作数栈顶int类型的元素
ireturn
4. dup
复制栈顶元素压入操作数栈
_new 'com/baidu/asmtest/TestByteCode'
dup // 将上一步new的对象栈顶复制一份
5. ldc 常量
读取常量池中的常量压入操作数栈
LDC "hello world"
6. xconst_n
将一个数字类型的常量压入操作数栈
x可以取值为 i/l/f/d
n可以取值为 m1,0,1,2,3,4,5
iconst_1 // 1
iconst_m1 // -1
lconst_1 // long类型1
dconst_1 // double类型1
7. bipush n, sipush n
将一个byte或者short类型的数字常量值压入操作数栈
对于bipush,n属于byte范围(-128~127)
对于sipush,n属于short范围(-32768~32767)
bipush 126
sipush 32766
8. 运算符
- xadd: 加法运算符
- xsub: 减法运算符
- xmul: 乘法运算符
- xdiv: 除法运算符
将栈顶的两个元素弹出并进行运算
iload 3 // 入栈
iload 0 // 入栈
iadd // 弹出栈顶两个元素,相加,将结果压入栈
9. 类型转换
- x2d: 转换为double类型,x可以取值为 i/l/f
- x2i: 转换为int类型, x可以取值为 d/l/f
- x2l: 转换为long类型,x可以取值为 i/d/f
- i2x: int转换为其它类型 x可以取值为 b/c/s
iload 3 // 将局部变量表中一个int类型的数字压入栈
i2d // 弹出栈顶元素,类型转换,将结果压入栈
三. Demo
定义一个方法,传入两个参数,两个参数分别相乘后再相加
public static double test2(long num1, int num2) {
return num1 * num1 + num2 * num2;
}
编译后的字节码
// 默认局部变量表中有两个元素,index为0,1存放long类型的a,index为2存放int类型的b
public static double test2(long a,int b) {
lload 0 // 将局部变量表index为0的元素压入栈
lload 0 // 将局部变量表index为0的元素压入栈
lmul // 弹出操作数栈 栈顶的两个元素,并进行乘法运算 num1 * num1, 将结果压入栈中
iload 2 // 将局部变量表index为2的元素压入栈, 为什么是2,因为第一个参数为long类型,局部变量表中占用两个slot,故第二个参数在局部变量表的index为2的位置
iload 2 // 同上
imul //弹出操作数栈 栈顶的两个元素,并进行乘法运算 num2 * num2, 将结果压入栈中
i2l // 将上一步的结果,转为long类型,并压入栈
ladd // 此时栈顶 为两个long类型的元素,进行相加
l2d // 返回值为double类型
dreturn
}
为了方便读者能够理解,我将注释写的非常详细,建议初学者仔细研读并且理解。
四. 总结
JVM操作指令有很多,本篇只讲常用的,后续无痕埋点Demo中会用到这些指令,务必理解。
下一篇我们会讲调用方法相关的操作指令。