内容简介:转载请注明出处:http://zhongmingmao.me/2019/01/03/jvm-basic-bytecode/
- JVM是 基于栈的计算模型
- 在 解析过程 中,每当为 Java方法 分配 栈帧 时
- 执行每条执行之前,JVM要求该指令的操作数已被压入操作数栈中
- 在执行指令时,JVM会将该指令所需要的操作数 弹出 ,并将该指令的结果重新 压入 栈中
iadd
- 执行iadd之前,栈顶的元素为int值1和int值2
- 执行iadd指令会将弹出这两个int,并将求得的和int值3压入栈中
- iadd只消耗栈顶的两个元素,iadd并不关心更远的元素,也不会对它们进行修改
dup + pop
- dup和pop只能处理非long和非double类型的值
- long类型和double类型需要占据两个栈单元,对应使用dup2和pop2
dup
- dup: 复制栈顶元素
- dup指令常用于复制new指令生成的 未经初始化 的引用
public void dup() {
Object o = new Object();
}
// 对应的字节码
public void dup();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new // class java/lang/Object
3: dup
4: invokespecial // Method java/lang/Object."<init>":()V
7: astore_1
8: return
- 执行new指令时,JVM将指向一块 已分配的但未初始化 的内存引用压入操作数栈
- invokespecial指令将要以这个引用为调用者,调用其构造器
- 该指令将 消耗 操作数栈上的元素,作为它的调用者和参数
- 因此,在这之前利用dup 指令 复制一份new指令的结果,并用来调用构造器
pop
- pop: 舍弃栈顶元素
- pop指令常用于 舍弃调用指令的返回结果
public static boolean judge() {
return false;
}
public void pop() {
judge();
}
// 对应的字节码
public void pop();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: invokestatic // Method judge:()Z
3: pop
4: return
- invokestatic指令依然会将返回值压入pop方法的操作数栈
- 因此JVM需要执行额外的pop指令,将返回值舍弃
加载常量
- iconst指令加载-1和5之间的int值
- bipush(sipush)指令加载一个字节(两个字节)所能代表的int值
- ldc指令加载常量池中的常量值
| 类型 | 常数指令 | 范围 |
|---|---|---|
| int/short/char/byte/boolean | iconst | [-1,5] |
| bipush | [-128,127] | |
| sipush | [-32768,32767] | |
| ldc | any int value | |
| long | lconst | 0,1 |
| ldc | any long value | |
| float | fconst | 0,1,2 |
| ldc | any float value | |
| double | dconst | 0,1 |
| ldc | any double value | |
| reference | aconst | null |
| ldc | String literal,Class literal |
异常
- 正常情况下,操作数栈的压入弹出都是 一条条 指令完成的
- 在抛出异常时,JVM会 清空 操作数栈上到所有内容,而后将异常实例的引用压入操作数栈
局部变量表
- Java方法 栈帧 的组成: 操作数栈+局部变量表
- 字节码程序将计算的结果 缓存 在局部变量表
- 局部变量表类似于一个 数组 ,依次存放
- this指针(针对实例方法)
- 所传入的参数
- 字节码中的局部变量
- 与操作数栈一样,long类型和double类型将占据两个存储单元
public void locals(long l, float f) {
{
int i = 0;
}
{
String s = "Hello Word";
}
}
// 对应的字节码
public void locals(long, float);
descriptor: (JF)V
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=3
0: iconst_0
1: istore 4
3: ldc // String Hello Word
5: astore 4
7: return
- locals是一个实例方法,局部变量表的第0个单元存放this指针
- 第一个参数为long类型,占用局部变量表的第1、2个单元
- 第二个参数为int类型,占用局部变量表的第3个单元
- 方法体内的两个代码块中,分别定义了局部变量i和s,两者的生命周期没有重合
- Java编译器将它们编排至同一单元,即局部变量表的第4个单元
-
istore 4和astore 4
- Java编译器在编译时就已经能确定操作数栈、局部变量表的大小以及参数个数
-
stack=1, locals=5, args_size=3
-
加载 + 存储
- 存储在局部变量表中的值,通常需要加载至操作数栈中,才能进行计算
- 得到的计算结果后再存储至局部变量表中
- 局部变量表的 加载 和 存储 指令都需要指明所加载单元的 下标
- 唯一直接作用于局部变量表的指令:iinc M N
- 将局部变量表的第M个单元中的int值增加N
- 常用于for循环中的自增量的更新
| 类型 | 加载指令 | 存储指令 |
|---|---|---|
| int/short/char/byte/boolean | iload | istore |
| long | lload | lstore |
| float | fload | fstore |
| double | dload | dstore |
| reference | aload | astore |
public void innc() {
for (int i = 0; i < 100; i++) {
}
}
// 对应的字节码
public void innc();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: bipush 100
5: if_icmpge 14
8: iinc 1, 1 // i++
11: goto 2
14: return
其他字节码
Java相关
- new:后跟目标类,生成该类 未初始化 的对象引用
- instanceof:后跟目标类,判断 栈顶元素 是否为目标类(接口)的实例, 是则压入1,否则压入0
- checkcast:后跟目标类,判断 栈顶元素 是否为目标类(接口)的实例,如果不是则 抛出异常
- athrow:将 栈顶异常 抛出
- monitorenter:为 栈顶对象 加锁
- monitorexit:为 栈顶对象 解锁
- getstatic/putstatic/getfield/putfield
- 附带用于定位目标字段的信息,但消耗的操作数栈元素个各不相同
- 如下图,将值v存储至对象obj的目标字段中
- invokestatic/invokespecial/invokevirtual/invokeinterface
- 这四个方法调用指令所消耗的操作数栈元素是根据 调用类型 以及 目标方法描述符 来确定的
- 在进行方法调用之前,需要依次压入调用者(invokestatic不需要)以及各个参数
putfiled
public class PutField {
private Object obj;
public void putFiled() {
obj = new Object();
}
}
// 对应的字节码
public void putFiled();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: new // class java/lang/Object
4: dup
5: invokespecial // Method java/lang/Object."<init>":()V
8: putfield // Field obj:Ljava/lang/Object;
11: return
invokevirtual
public int neg(int i) {
return -i;
}
public int foo(int i) {
return neg(neg(i));
}
// 对应的字节码
public int foo(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: aload_0
2: iload_1
3: invokevirtual // Method neg:(I)I
6: invokevirtual // Method neg:(I)I
9: ireturn
foo(2)的执行过程
数组相关
- newarray:新建 基本类型 的数组
- anewarray:新建 引用类型 的数组
- multianewarray:生成 多维数组
- arraylegth:求 数组长度
- 数组的 加载 和 存储 指令(区分类型)
public void array() {
int[] a = new int[10];
Object[] b = new Object[10];
int[][] c = new int[10][10];
int l = a.length;
}
// 对应的字节码
public void array();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=5, args_size=1
0: bipush 10
2: newarray int
4: astore_1
5: bipush 10
7: anewarray #2 // class java/lang/Object
10: astore_2
11: bipush 10
13: bipush 10
15: multianewarray #3, 2 // class "[[I"
19: astore_3
20: aload_1
21: arraylength
22: istore 4
24: return
| 数组类型 | 加载指令 | 存储指令 |
|---|---|---|
| byte/boolean | baload | bastore |
| char | caload | castore |
| short | saload | sastore |
| int | iaload | iastore |
| long | laload | lastore |
| float | faload | fastore |
| double | daload | dastore |
| reference | aaload | aastore |
控制流
- goto:无条件跳转
- tableswitch/lookupswitch:密集case/稀疏case
- 返回指令
- 除了返回指令外,其他控制流指令均附带一个或多个字节码偏移量,代表需要跳转到的位置
| 返回类型 | 返回指令 |
|---|---|
| void | return |
| int/short/char/byte/boolean | ireturn |
| long | lreturn |
| float | freturn |
| double | dreturn |
| reference | areturn |
转载请注明出处:http://zhongmingmao.me/2019/01/03/jvm-basic-bytecode/
访问原文「JVM基础 -- 字节码」获取最佳阅读体验并参与讨论
以上所述就是小编给大家介绍的《JVM基础 -- 字节码》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Access2003数据库开发典型范例
王樵民 / 第1版 (2006年8月1日) / 2006-8 / 42.00元
Access数据库管理软件是一套集数据管理、程序开发功能于一体的高级办公软件,是特别适合普通办公人员进行日常工作的有力助手。本书面向非计算机专业人员,通过多个实例讲解Access中的各种开发技术,介绍实际工作过程中应用软件的编制方法,以及与Excel等软件之间的信息互用技术等内容。能够帮助读者快速掌握Access数据库的开发技术,构建解决自己工作中实际问题的数据库管理系统,从而提高办公效率。一起来看看 《Access2003数据库开发典型范例》 这本书的介绍吧!