JVM字节码
开头
这一系列文章,主要是讲自动化埋点又叫无痕埋点,或者字节码插桩技术,写这个系列文章的目的是 偶然间发现,网上关于这方面的博客很少,所以我根据自己的一些实战经验,整理了这个系列的文章。
整个系列不会讲的太深入,以免造成初学者不知所云,通过一个Demo,让大家了解 java 字节码插桩的基本实现原理,为后续更深入的学习指引方向。
一. 概要
本篇主要是对Java栈,栈帧,局部变量表,操作数栈等进行一定讲解,结合实际demo和字节码文件,了解jvm虚拟机是如何执行我们的Java程序的。
JVM作为基础,是进行字节码插桩所必须要掌握的知识,但是本篇不会进行太深入的讲解,以免初学者陷入细节无法自拔或者不知所云。
二. 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. iconst_1
将常量1压入操作数栈
局部变量表:
index下标 | 0 |
---|---|
数值 | 1 |
局部变量表无变化
操作数栈:
1 |
---|
操作数栈 栈顶为1,操作数栈大小为1
2. istore 1
将操作数栈栈顶的1弹出,放入局部变量表1的位置
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表第0个位置为5,第1个位置为1,局部变量表的大小为2
操作数栈:
由于操作数栈弹出了栈顶的1,此时操作数栈为空
3. iload 1
将局部变量表中index为1的元素1压入操作数栈
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表无变化
操作数栈:
1 |
---|
1被压入操作数栈,栈顶为1, 操作数栈大小为1
4. iload 0
将局部变量表中index为0的元素5压入操作数栈
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表无变化
操作数栈:
5 |
---|
1 |
5被压入操作数栈,栈顶为5, 操作数栈大小为2
5. iadd
弹出操作数栈栈顶两个元素,进行相加,并将相加的结果6再次压入操作数栈
局部变量表:
index下标 | 0 | 1 |
---|---|---|
数值 | 5 | 1 |
局部变量表无变化
操作数栈:
6 |
---|
6. istore 2
将操作数栈栈顶的6弹出,放入局部变量表2的位置
局部变量表:
index下标 | 0 | 1 | 2 |
---|---|---|---|
数值 | 5 | 1 | 6 |
此时局部变量表的大小为3
操作数栈:
由于操作数栈弹出了栈顶的6,此时操作数栈为空
7. return
尽管这个方法是void,但是jvm虚拟机规范要求也要有return,如果返回int类型,则为 ireturn
局部变量表的最大size为3
操作数栈的最大size为2
三. 总结
至此,本篇主要讲了局部变量表和操作数栈在字节码中是如何操作,以及介绍了常见的几个操作指令。
- iconst_1 将常数1压入到操作数栈
- istore 弹出操作数栈顶的元素,放入到局部变量表对一个的下标位置
- iload 取出局部变量表对应下标位置的元素,压入操作数栈的栈顶
- iadd 弹出操作数栈顶的两个元素,进行相加,再将结果重新压入栈顶
JVM执行字节码,大多数是本地变量表,操作数栈,操作指令之间的协作,这也是理解字节码的关键所在。
本篇是基础,下一篇将详细讲解jvm操作符,如果对本章字节码操作指令不懂的,可以结合下一篇内容反复理解。