概述
字节码与数据类型
- 与数据类型有关的指令:
iload:用于从局部变量表中加载int型的数据到操作数栈中
arraylength:操作数组类型对象 - 与数据理性无关的指令:
goto:跳转
大部分的指令都没有支持整数类型byte、char、和short,boolean类型。编译器会在编译期或运行期将byte和short类型带符号扩展为相应int类型的数据。boolean和char也扩展为int
加载和存储指令
使用频率最高,包含加载和存储,用于将数据从栈帧的局部变量表和操作数栈之间来回传递
常用指令:
- 局部变量表到压栈指令:xload、xload_,其中x为i、l、f、d、a,n为0-3
- 常量入栈指令:bipush、sipush、ldc、ldc_w、ldc2_w等
- 出栈装入局部变量表指令:将一个数值从操作数栈存储到局部变量表xstore、xstore-
- 扩充局部变量表的访问索引指令:wide
例如:
iload_0:将局部变量表中索引为0位置上的数据压入操作数栈中
iload 4:将局部变量表中索引为4位置上的数据压入操作数栈中
操作数栈(Operate Stacks):存放操作数和返回的结果
局部变量表(Local Variables):相当于一个数组,与性能调优最密切的部分
局部变量压栈指令:xload(x为i、l、f、d、a)
常量入操作数栈指令:const系列、push系列、ldc系列,范围越来越大
const:特定常量入栈,入栈的常量隐含在指令中iconst_,i从-1到5,为具体数值
iconst-m1:将-1压入栈
int i= 3;iconst_3
int j =6;bipush 6
int k = 32768 ldc 数值对应的索引
push:bipush接收8位整数,sipush接收16位整数
ldc:接收一个8位参数,指向常量池中int、float、或者string的索引,将其内容压入栈中。ldc_w支持索引范围大于ldc,如果要压入的元素时long或者double类型的,使用ldc2_w指令
xstore:装入局部变量表指令:(x为i、l、f、d、a),xstore_n(n为0-3 为索引)
算术指令
对两个操作数栈上的值进行某种特定运算,并将结果重新压入操作数栈,指令分为整型和浮点型
int i = 10;
double j = 0.0;
System.out.println(i/j);//Infinity 无穷大
double d1 = 0.0;
double d2 = 0.0;
System.out.println(d1/d2); //NaN:Not a Number
add 加法;
sub 减法;
neg 取反:
iinc 自增;
++i和i++区别
比较指令:比较栈顶两个元素的大小,并将结果入栈
fcmpg 和 fcmpl 都是从栈中弹出两个数作比较,若a和b相等,压入0;若a大于b,压入1;若a小于b,压入-1。不同在于如果遇到NaN,fcmpg会压入1,fcmpl会压入-1
类型转换指令
将两种不同的数值类型进行相互转换
实现显示类型转换操作,或者处理字节码指令集中数据类型指令无法与数据类型一一对应问题
宽化类型转换
- 转换规则:x2x:int==》long==》float==》double
- 精度损失:不会因为目标过大损失信息,如int转long、double不会丢失信息。int、long转float,long转double会丢失信息。不会抛出异常
public static void upCast2(){
int i = 123123123;
float f = i;
System.out.println(f); //123123120
long l = 123123123123123123l;
double d = l;
System.out.println(d); // 123123123123123120l
}
窄化类型转换(强制转换):
- 转化规则:int==》byte、short、char;long==》int;float==》int/long;double==》int、long或者float
- 精度损失:可能导致结果具备不同的正负号,不抛出异常
public static void downCast(){
int i = 128;
byte b = (byte)i;
System.out.println(b);//-128
}
- NaN 无穷大情况
对象的创建与访问指令
创建指令
- 创建类实例指令:new ,接收一个操作数为指向字符串常量池的索引
- 创建数组指令:
1)newarray:创建基本类型数组
2)anewarray:创建引用类型数组
3)multianewarray:创建多维数组
字段访问指令
- 访问类字段(static)指令:getstatic、putstatic
- 访问类实例(非static)字段:getfield、putfield
getstatic含有一个操作数,为指向常量池的Fieldref索引,作用是获取Fieldref指定的对象或者值,将其压入操作数栈
get相当于压入栈,put相当于出栈
数组操作指令
- 把一个数组元素加载到操作数栈中xaload
- 将一个操作数栈的值存储到数组元素中xastore
xastore针对数组,iastore,用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要准备三个元素,值、索引、数组引用,iastore会弹出三个值并将值赋给数组中指定索引的位置
xaload在执行时,要求操作数中栈顶元素为数组索引i,栈顶顺位第2元素为数组引用a,该指令会弹出栈顶这两个元素,并将a[i]重新压入栈 - 获取数组长度指令:arraylength
类型检查指令
- checkcast检查类型强制转换是否可以进行,不会改变操作数栈,否则抛出ClasscastException
- instanceof用来判断对象是否是某一个类的实例,将结果压入操作数栈中
方法调用与返回指令
方法调用指令
- invokevertual:调用对象的实例方法,支持多态
- invokeinterface:调用接口方法
- invokespecial:特殊处理的实例方法,构造器、私有方法、父类方法,不存在方法重写
- invokestatic:调用类中的static方法
- invokedynamic
package chapter202;
import java.util.Date;
public class MethodInvokeReturnTest {
/**
* 方法调用指令:invokespecial 静态分派
*/
public void invoke1(){
// 构造器方法
Date date = new Date();
// 构造器方法
Thread thread = new Thread();
// 父类方法
super.toString();
// 私有方法
methodPrivate();
}
private void methodPrivate(){
}
/**
* 方法调用指令:invokestatic,静态分派
*/
public void invoke2(){
// 静态方法
methodStatic();
}
private static void methodStatic(){
}
/**
* 方法调用指令:invokeinterface
*/
public void invoke3(){
Thread thread = new Thread();
((Runnable)thread).run();
Comparable<Integer> com = null;
com.compareTo(124);
}
/**
* 方法调用指令:invokevirtual 动态方法
*/
public void invoke4(){
System.out.println("hello");
Thread t1 = null;
t1.run();
}
}
方法返回指令
- ireturn 有返回值,将操作数栈栈顶元素弹出,将元素返回调用者函数的操作数栈中
- return 无返回值
操作数栈管理指令
- 将一个或两个元素从栈顶弹出:pop,pop2
- 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2,dup_x1,dup2_x1,dup_x2,dup2_x2
1)不带_x是复制栈顶数据并压入栈顶
2)带_x是复制栈顶数据并压入栈顶一下的某个位置,dup和x的系数相加 - 交换两个slot数值位置:swap
- nop:调试、占位
控制转移指令
-
比较指令
-
条件跳转指令
在条件跳转指令之前,一般先用比较指令进行栈顶元素准备。ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull
-
比较条件跳转指令
将比较和跳转合二为一
-
多条件分支跳转
没有break就没有无条件跳转goto语句
-
无条件跳转
用于指定指令的偏移量,指令执行的目的就是跳转到偏移量给定的位置处。
异常处理指令
异常及异常的处理:
过程一:异常对象的生成过程 ==》throw(手动/自动) ==》指令:athrow
过程二:异常的处理:抓抛模型 try-catch-finally ==》使用异常表
抛出异常指令:throw语句对应的是athrow指令,自动异常不会对应指令
异常表:
如果一个方法定义了try/catch try/finally的异常处理,就会创建一个异常表。
1)起始位置
2)结束位置
3)程序计数器记录的代码处理的偏移地址
4)被捕获的异常类在常量池中的索引
字节码:
0 new #2 <java/io/File>
3 dup
4 ldc #3 <d:a.txt>
6 invokespecial #4 <java/io/File.<init> : (Ljava/lang/String;)V>
9 astore_1
10 new #5 <java/io/FileInputStream>
13 dup
14 aload_1
15 invokespecial #6 <java/io/FileInputStream.<init> : (Ljava/io/File;)V>
18 astore_2
19 ldc #7 <hello!>
21 astore_3
22 goto 38 (+16)
25 astore_2
26 aload_2
27 invokevirtual #9 <java/io/FileNotFoundException.printStackTrace : ()V>
30 goto 38 (+8)
33 astore_2
34 aload_2
35 invokevirtual #11 <java/lang/RuntimeException.printStackTrace : ()V>
38 return
public String func(){ //方法返回值为hello
String str = "hello";
try{
return str;
}finally {
str = "abc";
}
}
同步控制指令
方法级同步:隐式的,无须通过字节码指令来控制。可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法声明是否我同步方法。
调用方法时,调用指令检查方法的ACC_SYNCHRONIZED访问标志是否设置:
- 如果设置了,先持有同步锁,然后执行,最后方法完成时释放
- 方法执行期间,线程持有同步锁,其他线程无法获得
- 如果一个同步方法抛出异常,并且在方法内部无法处理,那么这个同步方法所持有的锁将在异常跑到同步方法之外时自动释放
方法内指令序列的同步
同步一段指令集序列:monitorenter、monitorexit
monitorenter:进入监视器 当前对象0==》1
monitorexit:退出监视器 当前对象1==》0