上篇博客 【我的ASM学习进阶之旅】 09 介绍ASM的Core API 的Method的字节码指令https://ouyangpeng.blog.csdn.net/article/details/112574716
我们将了字节码指令的基本概念,下面这篇博客我们来实际操作讲解一下字节码指令。
一、示例讲解
让我们看一些基本示例,以更具体地了解字节码指令的工作方式。
1.1 Java示例类源码
以下bean类:
package pkg;
public class Bean {
private int f;
public int getF() {
return this.f;
}
public void setF(int f) {
this.f = f;
}
}
1.2 对应的ByteCode字节码
对应的ByteCode字节码为:
// class version 52.0 (52)
// access flags 0x21
public class pkg/Bean {
// compiled from: Bean.java
// access flags 0x2
private I f
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lpkg/Bean; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public getF()I
L0
LINENUMBER 7 L0
ALOAD 0
GETFIELD pkg/Bean.f : I
IRETURN
L1
LOCALVARIABLE this Lpkg/Bean; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public setF(I)V
L0
LINENUMBER 11 L0
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean.f : I
L1
LINENUMBER 12 L1
RETURN
L2
LOCALVARIABLE this Lpkg/Bean; L0 L2 0
LOCALVARIABLE f I L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
}
1.3 getter方法的字节码
getter方法的字节码核心代码为:
ALOAD 0
GETFIELD pkg/Bean.f : I
IRETURN
-
第一条指令读取局部变量0,该局部变量在创建此方法调用的框架期间已初始化为0,并将此值压入操作数堆栈。
-
第二条指令从堆栈中弹出此值,即this,并压入此对象的f字段,即this.f.
-
最后一条指令从堆栈中弹出该值,并将其返回给调用方。
此方法的执行帧的连续状态如下图所示。
getF方法的连续frame状态:
- a)初始状态,
- b)在ALOAD 0之后
- c)在GETFIELD之后
1.3 setter方法的字节码
setter方法的字节码核心代码为:
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean.f : I
RETURN
- 与之前一样,第一条指令将其压入操作数堆栈。
- 第二条指令将局部变量1推送到该方法调用的框架的创建过程中,该局部变量使用f参数值进行了初始化。
- 第三条指令弹出这两个值,并将int值存储在所引用对象的f字段中,即在this.f中。
- 最后一条指令在源代码中是隐式的,而在编译后的代码中是必需的,它会破坏当前执行框架并返回给调用者。
此方法的执行帧的连续状态如下图所示。
setF方法的连续帧状态:
- a)初始状态
- b)ALOAD 0之后
- c)ILOAD 1之后
- d)PUTFIELD之后
1.4 构造方法的字节码
Bean
类还具有由编译器生成的默认公共构造函数,因为程序员没有定义任何显式构造函数。
此默认的公共构造函数生成为
Bean(){
super();
}
该构造函数的字节码如下:
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lpkg/Bean; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
核心代码为
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
- 第一条指令将其压入操作数堆栈。
- 第二条指令从堆栈中弹出该值,并调用Object类中定义的方法。 这对应于super()调用,即对超类Object的构造函数的调用。 您可以在此处看到,在编译类和源类中,构造函数的名称不同:在编译类中,它们始终被命名为,而在源类中,它们具有定义它们的类的名称。
- 最后,最后一条指令返回给调用者。
1.5 稍微复杂的setter方法
现在让我们考虑一个稍微复杂一些的setter方法:
public void checkAndSetF(int f) {
if (f >= 0) {
this.f = f;
} else {
throw new IllegalArgumentException();
}
}
此新的setter方法的字节码如下:
// access flags 0x1
public checkAndSetF(I)V
L0
LINENUMBER 16 L0
ILOAD 1
IFLT L1
L2
LINENUMBER 17 L2
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean.f : I
GOTO L3
L1
LINENUMBER 19 L1
FRAME SAME
NEW java/lang/IllegalArgumentException
DUP
INVOKESPECIAL java/lang/IllegalArgumentException.<init> ()V
ATHROW
L3
LINENUMBER 21 L3
FRAME SAME
RETURN
L4
LOCALVARIABLE this Lpkg/Bean; L0 L4 0
LOCALVARIABLE f I L0 L4 1
MAXSTACK = 2
MAXLOCALS = 2
}
核心代码为
ILOAD 1
IFLT L1
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean.f : I
GOTO L3
L1
NEW java/lang/IllegalArgumentException
DUP
INVOKESPECIAL java/lang/IllegalArgumentException.<init> ()V
ATHROW
L3
RETURN
-
第一条指令将局部变量1(初始化为f)压入操作数堆栈。
-
IFLT指令从堆栈中弹出该值,并将其与0进行比较。如果它小于(LT)0,则跳转到L1标签指定的指令,否则不执行任何操作,并继续执行下一条指令。
-
接下来的三个指令与setF方法中的指令相同。
-
GOTO指令无条件地跳转到由L3标签指定的指令,即RETURN指令。
-
L1和L3标签之间的指令创建并引发异常:NEW指令创建异常对象并将其压入操作数堆栈。
-
DUP指令在堆栈上复制此值。
-
INVOKESPECIAL指令将弹出这两个副本之一,并在其上调用异常构造函数。
-
最后,ATHROW指令弹出剩余的副本并将其作为异常抛出(因此执行不会继续执行下一条指令)
1.6 异常处理程序
没有捕获异常的字节码指令:相反,方法的字节码与一系列异常处理程序相关联,这些异常处理程序指定了在方法的给定部分抛出异常时必须执行的代码。
异常处理程序类似于try catch块:它具有一个范围(该范围是与try块的内容相对应的一系列指令)和一个处理程序(与catch块的内容相对应)。
该范围由开始和结束标签指定,而处理程序由开始标签指定。
例如下面的源代码:
public static void sleep(long d) {
try {
Thread.sleep(d);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
对应的sleep方法的字节码为:
// access flags 0x9
public static sleep(J)V
TRYCATCHBLOCK L0 L1 L2 java/lang/InterruptedException
L0
LINENUMBER 25 L0
LLOAD 0
INVOKESTATIC java/lang/Thread.sleep (J)V
L1
LINENUMBER 28 L1
GOTO L3
L2
LINENUMBER 26 L2
FRAME SAME1 java/lang/InterruptedException
ASTORE 2
L4
LINENUMBER 27 L4
ALOAD 2
INVOKEVIRTUAL java/lang/InterruptedException.printStackTrace ()V
L3
LINENUMBER 29 L3
FRAME SAME
RETURN
L5
LOCALVARIABLE e Ljava/lang/InterruptedException; L4 L3 2
LOCALVARIABLE d J L0 L5 0
MAXSTACK = 2
MAXLOCALS = 3
扩展
-
【ASM字节码编程 | 用字节码增强技术给所有方法加上TryCatch捕获异常并输出】
https://zhuanlan.zhihu.com/p/132354241 -
【java-如何通过ASM在字节码中捕获运行时异常】
http://www.cocoachina.com/articles/484620