这一系列文章,主要是讲自动化埋点又叫无痕埋点,或者字节码插桩技术,写这个系列文章的目的是 偶然间发现,网上关于这方面的博客很少,所以我根据自己的一些实战经验,整理了这个系列的文章。
整个系列不会讲的太深入,以免造成初学者不知所云,通过一个Demo,让大家了解 java 字节码插桩的基本实现原理,为后续更深入的学习指引方向。
本篇主要是对Java栈,栈帧,局部变量表,操作数栈等进行一定讲解,结合实际demo和字节码文件,了解jvm虚拟机是如何执行我们的Java程序的。
JVM作为基础,是进行字节码插桩所必须要掌握的知识,但是本篇不会进行太深入的讲解,以免初学者陷入细节无法自拔或者不知所云。
Java 是以方法为执行单位,而我们的字节码插桩也主要是关心JAVA栈
每创建一个线程就会对应创建一个Java栈,所以Java栈也是"线程私有"的内存区域,而这个栈对应也会包含很多栈帧,每调用一个方法时,都会向Java栈中压入一个栈帧。
每个栈帧中包含:
定义一个test静态方法,无返回值,方法需传入一个参数,方法体内是简单的数值相加
public class TestByteCode {
public static void main(String[] args) {
test(5);
}
public static void test(int num) {
int a = 1;
int c = a + num;
}
}
编译后的java字节码
(* 查看字节码文件,大家可以在Android studio 上安装ASM Bytecode Viewer插件)
public static void test(int a) {
iconst_1
istore 1
iload 1
iload 0
iadd
istore 2
return
}
方法默认需要传入一个参数 int a, 所以局部变量表中index为0的位置为传入的5
局部变量表
index下标 | 0 |
---|---|
数值 | 5 |
刚进入方法体,局部变量表第0个位置的数值为5,局部变量表的大小为1
操作数栈:
操作数栈为空
将常量1压入操作数栈
局部变量表:
index下标 | 0 |
---|---|
数值 | 1 |
局部变量表无变化
操作数栈:
1 |
---|
操作数栈 栈顶为1,操作数栈大小为1
将操作数栈栈顶的1弹出,放入局部变量表1的位置
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表第0个位置为5,第1个位置为1,局部变量表的大小为2
操作数栈:
由于操作数栈弹出了栈顶的1,此时操作数栈为空
将局部变量表中index为1的元素1压入操作数栈
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表无变化
操作数栈:
1 |
---|
1被压入操作数栈,栈顶为1, 操作数栈大小为1
将局部变量表中index为0的元素5压入操作数栈
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表无变化
操作数栈:
5 |
---|
1 |
5被压入操作数栈,栈顶为5, 操作数栈大小为2
弹出操作数栈栈顶两个元素,进行相加,并将相加的结果6再次压入操作数栈
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表无变化
操作数栈:
6 |
---|
将操作数栈栈顶的6弹出,放入局部变量表2的位置
局部变量表:
index下标 | 0 | 1 | 2 |
---|---|---|---|
数值 | 5 | 1 | 6 |
此时局部变量表的大小为3
操作数栈:
由于操作数栈弹出了栈顶的6,此时操作数栈为空
尽管这个方法是void,但是jvm虚拟机规范要求也要有return,如果返回int类型,则为 ireturn
局部变量表的最大size为3
操作数栈的最大size为2
至此,本篇主要讲了局部变量表和操作数栈在字节码中是如何操作,以及介绍了常见的几个操作指令。
JVM执行字节码,大多数是本地变量表,操作数栈,操作指令之间的协作,这也是理解字节码的关键所在。
本篇是基础,下一篇将详细讲解jvm操作符,如果对本章字节码操作指令不懂的,可以结合下一篇内容反复理解。