Java-字节码指令

  字节码是JVM的机器语言。JVM加载类文件时,对类中的每个方法,都会得到一个字节码流。这些字节码流保存在JVM的方法区中。在程序运行过程中,当一个方法被调用时,它的字节码流就会被执行。

  • 基础简介
  1. 方法的字节码流就是JVM的指令(instruction)序列。
  2. Java虚拟机的指令:由一个字节长度、代表着某种特定操作含义的数字(操作码Opcode)以及跟随其后的0-n个代表此操作所需参数(操作数Operaands)而构成。即指令=操作码+操作数,虚拟机中许多指令并不包含操作数,只有一个操作码。
  3. JVM中,所有的计算都是围绕栈。因为JVM没有存储任意数值的寄存器,所有的操作数在计算开始之前,都必须先压入栈中

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package com.xxo.demo.util;

/**
* Created by xiaoxiaomo on 2016/3/31.
*/
public class LineNumberDemo {
private String c = "c" ;
public static void main(String[] args) {
String a = "abc" ;
String b = "ab" ;
}
}
  • 字节码和数据类型

在java虚拟机中,大多数的指令都包含了操作数所对应的数据类型。这一点很重要,理解了这一点很多指令都能联想记忆,例如:iload指令用于从局部变量表中加载int型的数据到操作数栈中,而fload就是加载float类型的数据。下面列出:

  1. i–int
  2. l–long
  3. s–short
  4. b–byte
  5. c–char
  6. f–float
  7. d–double
  8. a–reference

加载和存储指令

每个操作如果需要从操作栈中读参数,则总是将这些参数出栈,如果操作有结果,总是会将结果入栈

加载:将数据从栈帧的局部变量表加载到操作数栈;
存储:将操作数栈存储到栈帧的局部变量表;

  • 1、将一个局部变量加载到操作栈-load。例如: iloadiload_nlloadlload_nfloadfload_ndloaddload_naloadaload_n
  • 2、将一个数值从操作数栈存储到局部变量表-store。例如: istoreistore_nlstorelstore_nfstorefstore_ndstoredstore_nastoreastore_n
  • 3、将一个常量加载到操作数栈。例如: bipushsipushldcldc_wldc2_waconst_nulliconst_m1iconst_ilconst_lfconst_fdconst_d

例如,下面事例1:

1
2
3
4
5
6
7
8
9
10
11
12
package com.xxo.demo.util;

/**
* Created by xiaoxiaomo on 2016/4/1.
*/
public class LoadStoreDemo {
public static void main(String[] args) {
int a = 128 ;//会自动拆箱为short
int b = 22 ;//会自动拆箱为byte
String c ="momo" ;
}
}

通过javap -c编译后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class com.xxo.demo.util.LoadStoreDemo extends java.lang.Object{
public static java.lang.String a;

public com.xxo.demo.util.LoadStoreDemo();//默认构造方法
Code:
0: aload_0 //一个引用类型的局部变量加载到操作栈
1: invokespecial #1; //Method java/lang/Object."<init>":()V //
4: return //没有返回值

public static void main(java.lang.String[]); //main方法
Code:
0: sipush 128 //将一个short类型(int自动拆箱)常量加载到操作数栈(压入栈顶)
3: istore_1 //将数值从操作数栈存储到局部变量表,即a中(弹栈)
4: bipush 22 //将一个byte类型(自动拆箱)常量压入栈顶
6: istore_2 //将数值从操作数栈存储到局部变量表中,即b中
7: ldc #2; //String momo //将字符串常量压入栈顶
9: astore_3 //将数值从操作数栈存储到局部变量表c中
10: return //没有返回值

}

运算指令

算术指令,用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶
运算指令分为两种整型(int,long)浮点型(float,double),对于没有直接支持 byte、short、char和boolean的算术指令使用int。

下面来看一下所有的算术指令

  • 加法指令iaddladdfadddadd
  • 减法指令isublsubfsubdsub
  • 乘法指令imullmulfmuldmul
  • 除法指令idivldivfdivddiv
  • 求余指令iremlremfremdrem
  • 取反指令ineglnegfnegdneg
  • 位移指令ishlishriushrlshllshrlushr
  • 按位或指令iorlor
  • 按位与指令iandland
  • 按位异或指令ixorlxor
  • 局部变量自增指令iinc(是少见的直接更新一个局部变量而无需在操作数栈中进行读写的指令)
  • 比较指令dcmpgdcmplfcmpgfcmpllcmp

java实例代码2:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Created by xiaoxiaomo on 2016/4/1.
*/
public class OperatorDemo {

public static void main(String[] args) {
int a = 127 ;
int b = 127000000 ;
int d = a+b ;
long c = a*b ;

}
}

通过javap -c编译后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class com.xxo.demo.util.OperatorDemo extends java.lang.Object{
public com.xxo.demo.util.OperatorDemo();//默认构造方法
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]); //main方法
Code:
0: bipush 127 //将一个常量加载到操作数栈
2: istore_1 //将常量从操作数栈存储到局部变量表中,即a中
3: ldc #2; //int 127000000 //int类型
5: istore_2 //将数值从操作数栈存储到局部变量表
6: iload_1 //将一个局部变量a加载到操作栈
7: iload_2 //将一个局部变量b加载到操作栈
8: iadd //把上面两个操作栈中的int数据类型相加
9: istore_3 //把相加的结果d存到布局变量表
10: iload_1 //重复第6步操作
11: iload_2 //重复第7步操作
12: imul //相乘
13: i2l //int类型转为long类型
14: lstore 4 //把相乘的结果c存到布局变量表
16: return //没有返回值
}

类型转换指令

类型转换指令,数据类型进行相互转换,一般用于实现用户代码的显式类型转换操作,或者用来处理 Java 虚拟机字节码指令集中指令非完全独立的问题。

宽化类型转换

  • int 类型到 long、float 或者 double 类型
  • long 类型到 float、double 类型
  • float 类型到 double 类型

窄化类型转换

i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l 和 d2f。窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级,转换过程很可能会导致数值丢失精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
*
* Created by xiaoxiaomo on 2016/4/2.
*/
public class TypeCastDemo {

public void typeCase(){
int i = 128 ;
short j = (short) i;
double d = i*5.20 ;
int z = (int) d;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class com.xxo.demo.util.TypeCastDemo extends java.lang.Object{
public com.xxo.demo.util.TypeCastDemo();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public void typeCase();//typeCase方法(下面简单的介绍一下)
Code:
0: sipush 128 //将一个常量压入栈顶
3: istore_1 //将栈顶的值弹出(弹栈)存储到局部变量表中,i中
4: iload_1 //将局部变量中的i加载到操作栈
5: i2s //将i的int类型转为short
6: istore_2 //并存储到局布变量表中,j中
7: iload_1 //加载局部变量i,到操作栈
8: i2d //将操作栈变量i的类型转为double
9: ldc2_w #2; //double 5.2d //
12: dmul //将操作栈中的值相乘
13: dstore_3 //将相乘的结果存储到局部变量表,d中
14: dload_3 //加载局部变量表中的d,到操作栈
15: d2i //将double类型的变量d转为int类型
16: istore 5 //把结果存储到局部变量表中,z中
18: return //没有返回值

}

对象创建与操作

虽然类实例和数组都是对象,但 Java 虚拟机对类实例和数组的创建与操作使用了不同的字节码指令:

创建类实例的指令new
创建数组的指令newarrayanewarraymultianewarray
访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者成为实例变量)的指令getfieldputfieldgetstaticputstatic
把一个数组元素加载到操作数栈的指令baloadcaloadsaloadialoadlaloadfaloaddaloadaaload
将一个操作数栈的值储存到数组元素中的指令bastorecastoresastoreiastorefastoredastoreaastore
取数组长度的指令arraylength
检查类实例类型的指令instanceofcheckcas

看看下面的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 对象创建与操作
* Created by xiaoxiaomo on 2016/4/2.
*/
public class Instantiation {

public static String NAME = "LIU BI" ;
public static void main(String[] args) {
int[] arr1 = new int[5] ;
arr1[0] = 86 ;
arr1[2] = 19 ;
String[] strs = {"2","xiao","mo" , NAME } ;
String tc = new String("abc") ;
}
}

编译后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class com.xxo.demo.util.Instantiation extends java.lang.Object{
public static java.lang.String NAME;

public com.xxo.demo.util.Instantiation();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]); //main方法
Code:
0: iconst_5 //将常量5压入栈顶
1: newarray int //创建一个int类型的数组
3: astore_1 //将数组存储到局部变量表中,arr1中
4: aload_1 //将局部变量arr1加载到操作栈
5: iconst_0 //将常量0加载到操作数栈,
6: bipush 86 //将一个byte类型常量86加载到操作数栈
8: iastore //将一个操作数栈的值86储存到数组元素中,arr[0]中
9: aload_1 //将局部变量arr1加载到操作栈
10: iconst_2 //将常量2加载到操作数栈
11: bipush 19 //将一个byte类型常量19加载到操作数栈
13: iastore //将一个操作数栈的值19储存到数组元素中,arr[2]中
14: iconst_4 // 将常量4压入栈顶
15: anewarray #2; //class java/lang/String //创建一个String类型的数组
18: dup //直接操作操作数栈
19: iconst_0 //将常量0加载到操作数栈,
20: ldc #3; //String 2 //字符串常量加载到操作数栈,2
22: aastore //将引用类型的操作数栈的值储存到数组元素中,strs[0]中
23: dup //直接操作操作数栈
24: iconst_1 //将常量1加载到操作数栈,
25: ldc #4; //String xiao //字符串常量xiao加载到操作数栈
27: aastore //将引用类型的操作数栈的值储存到数组元素中,strs[1]中
28: dup //直接操作操作数栈
29: iconst_2 //将常量2加载到操作数栈,
30: ldc #5; //String mo //字符串常量加载到操作数栈
32: aastore //将引用类型的操作数栈的值储存到数组元素中,strs[2]中
33: dup //直接操作操作数栈
34: iconst_3 //将常量3加载到操作数栈,
35: getstatic #6; //Field NAME:Ljava/lang/String; //获取静态变量
38: aastore //将引用类型的操作数栈的值储存到变量中
39: astore_2 //将引用类型的操作数栈的值储存到变量中
40: new #2; //class java/lang/String //实例化一个对象
43: dup //直接操作操作数栈
44: ldc #7; //String abc //将字符串常量abc加载到操作数栈
46: invokespecial #8; //Method java/lang/String."<init>":(Ljava/lang/String;)V //调用实例方法new String
49: astore_3 //将引用类型的操作数栈的值储存到变量中,tc中
50: return //没有返回值

static {}; //静态区域
Code:
0: ldc #9; //String LIU BI //字符串常量‘LIU BI’加载到操作数栈
2: putstatic #6; //Field NAME:Ljava/lang/String;//实例静态字段
5: return //没有返回值
}

操作数栈管理指令

虚拟机提供了一些用于直接操作操作数栈的指令,包括:pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2 和 swap。如上事例,直接操作数栈

控制转移指令

控制转移指令,可以让JVM有条件或无条件地从指定指令转移到指令的下一条指令继续执行程序。控制转移指令包括有:

条件分支ifeqifltifleifneifgtifgeifnullifnonnullif_icmpeqif_icmpneif_icmplt, if_icmpgtif_icmpleif_icmpgeif_acmpeqif_acmpne
复合条件分支tableswitchlookupswitch
无条件分支gotogoto_wjsrjsr_wret

  • 事例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.xxo.demo.util;

/**
* 流程控制语句
* Created by xiaoxiaomo on 2016/4/2.
*/
public class ControlStatement {

public int max(int a , int b) {
int max = a ;
if( b > a ){
max = b ;
}
return max ;
}

public static void formatPrint( int[] arr ){
System.out.print("[");
for (int i = 0 ; i < arr.length ; i++ ){
System.out.print(arr[i]);

if( i < arr.length -1 ){
System.out.print(",");
}
}
System.out.print("]");
}
}

反编译后字节码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class com.xxo.demo.util.ControlStatement extends java.lang.Object{
public com.xxo.demo.util.ControlStatement();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return

public int max(int, int); //max方法
Code:
0: iload_1 //将一个局部变量加载到操作数栈,a
1: istore_3 //将上面操作数栈存储到局部变量表中,max
2: iload_2 //将变量压入操作数栈中,b
3: iload_1 //将变量压入操作数栈中,a
4: if_icmple 9 //比较栈顶的两个值的大小,如果为false回调到第9条字节码处执行
7: iload_2 //如果判断为true,会加载栈顶的值,b
8: istore_3 //将b值存储到局部变量max中
9: iload_3 //如果判断为false,则会加载局部变量数值,max
10: ireturn //返回一个int类型数据

public static void formatPrint(int[]);//一个静态方法
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;//获取输出流
3: ldc #3; //String [ //将字符串常量‘[’压入栈顶(和加载到操作数栈一个意思)
5: invokevirtual #4; //Method java/io/PrintStream.print:(Ljava/lang/String;)V //io输出流
8: iconst_0 //将常量0加载到操作数栈
9: istore_1 //将栈顶的值出栈存储到局部变量表中,i=0
10: iload_1 //加载局部变量i到操作栈,此时i已有值
11: aload_0 //加载引用类型的变量到操作栈中,arr
12: arraylength //获取数组长度,arr.length
13: if_icmpge 47 //判断局部变量i是否大于等于数组长度,如果true,跳到47条指令
16: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;//获取输出流
19: aload_0 //加载引用类型的变量到操作栈中,arr;同第11条指令
20: iload_1 //加载int类型的变量到操作栈中,即i
21: iaload //把arr的数组元素加载到操作数栈中
22: invokevirtual #5; //Method java/io/PrintStream.print:(I)V //调用输出打印
25: iload_1 //加载局部变量i到操作栈
26: aload_0 //加载引用类型的变量到操作栈中,arr;同第11条指令
27: arraylength ////获取数组长度,arr.length;同第12条指令
28: iconst_1 //将常量1加载到操作数栈,
29: isub //在操作数栈中使用数组长度减去变量1
30: if_icmpge 41 //如果变量i大于等于数组长度,跳转到第41条指令
33: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;//获取输出流
36: ldc #6; //String , //将字符串常量‘,’压入栈顶
38: invokevirtual #4; //Method java/io/PrintStream.print:(Ljava/lang/String;)V调用输出打印
41: iinc 1, 1 //变量i自增1(局部变量表中的第1个位置的值(即i)加1)
44: goto 10 //回调到第10条指令,继续执行
47: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;//获取输出流
50: ldc #7; //String ] //将字符串常量‘]’压入栈顶
52: invokevirtual #4; //Method java/io/PrintStream.print:(Ljava/lang/String;)V调用输出打印
55: return //没有返回值

}

方法调用和返回指令

  • 四条指令用于方法调用

invokevirtual:指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
invokeinterface:指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
invokespecial:指令用于调用一些需要特殊处理的实例方法包括实例初始化方法、私有方法和父类方法
invokestatic:指令用于调用类方法(static方法)

  • 方法返回指令

ireturn:(当返回值是 boolean、byte、char、short 和 int 类型时使用)
lreturn:返回long类型
freturn:返回floalt类型
dreturn:返回double类型
areturn:返回对象
return :指令表示为void的方法实例初始化方法类和接口的类初始化方法使用。

抛出异常

在程序中显式抛出异常的操作会由 athrow 指令实现,除了这种情况,还有别的异常会在其它 Java 虚拟机指令检测到异常状况时由虚拟机自动抛出。

同步

JVM可以支持方法级的同步方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。

  • 方法级的同步:是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中

虚拟机可以从方法常量池中的方法表结构(method_info Structure)中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。

  • 同步一段指令集序列:通常是由Java语言中的synchronized块来表示的。

JVM的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现 synchronized 关键字需要编译器与 Java 虚拟机两者协作支持。

结构化锁定(Structured Locking):是指在方法调用期间每一个管程退出都与前面的管程进入相匹配的情形。因为无法保证所有提交给 Java 虚拟机执行的代码都满足结构化锁定,所以 Java 虚拟机允许(但不强制要求)通过以下两条规则来保证结构化锁定成立。假设 T 代表一条线程,M 代表一个管程的话:

T在方法执行时持有管程 M 的次数必须与 T 在方法完成(包括正常和非正常完成)时释放管程 M 的次数相等。
找方法调用过程中,任何时刻都不会出现线程 T 释放管程 M 的次数比 T 持有管程 M 次数多的情况。
请注意,在同步方法调用时自动持有和释放管程的过程也被认为是在方法调用期间发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* Created by xiaoxiaomo on 2016/4/2.
*/
public class SynDemo {

Integer num=0;
private Lock lock=new ReentrantLock();
private AtomicInteger number=new AtomicInteger(0);

public static void main(String[] args) {
new SynDemo().syn() ;
}

private void syn() {
//启动10个线程
for (int i = 0; i < 10; i++) {
new Thread(new TestSyn()).start() ;
//new Thread(new LockTest()).start() ;
//new Thread(new LockFreeTest()).start() ;
}
}

class TestSyn implements Runnable{

@Override
public void run() {
synchronized (num) { // 间接的被转化为单线程了,相当于串行执行代码
if( num < 5 ){
System.out.println(String.format(
"name:%s, num:%s",Thread.currentThread().getName(),num));
num ++ ;
}
System.out.println(num);
}
}
}

/**
* 使用lock方式
*/
class LockTest implements Runnable{

@Override
public void run() {

lock.lock();
if (num < 5) {
System.out.println(String.format("thread:%s num:%s Do some thing", Thread.currentThread().getName(), num));
num++;

}
lock.unlock();
}
}

/**
* 使用原子类
*/
class LockFreeTest implements Runnable{

@Override
public void run() {

if (number.get() < 5) {
System.out.println(String.format("thread:%s num:%s Do some thing", Thread.currentThread().getName(),
number.getAndIncrement()));
//num++;
// System.out.println(num);

}
}
}
}

编译后,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class com.xxo.demo.util.SynDemo extends java.lang.Object{
java.lang.Integer num;

public com.xxo.demo.util.SynDemo();
Code:
0: aload_0
1: invokespecial #3; //Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: putfield #5; //Field num:Ljava/lang/Integer;
12: aload_0
13: new #6; //class java/util/concurrent/locks/ReentrantLock
16: dup
17: invokespecial #7; //Method java/util/concurrent/locks/ReentrantLock."<init>":()V
20: putfield #2; //Field lock:Ljava/util/concurrent/locks/Lock;
23: aload_0
24: new #8; //class java/util/concurrent/atomic/AtomicInteger
27: dup
28: iconst_0
29: invokespecial #9; //Method java/util/concurrent/atomic/AtomicInteger."<init>":(I)V
32: putfield #1; //Field number:Ljava/util/concurrent/atomic/AtomicInteger;
35: return

public static void main(java.lang.String[]);
Code:
0: new #10; //class com/xxo/demo/util/SynDemo
3: dup
4: invokespecial #11; //Method "<init>":()V
7: invokespecial #12; //Method syn:()V
10: return

static java.util.concurrent.locks.Lock access$000(com.xxo.demo.util.SynDemo);
Code:
0: aload_0
1: getfield #2; //Field lock:Ljava/util/concurrent/locks/Lock;
4: areturn

static java.util.concurrent.atomic.AtomicInteger access$100(com.xxo.demo.util.SynDemo);
Code:
0: aload_0
1: getfield #1; //Field number:Ljava/util/concurrent/atomic/AtomicInteger;
4: areturn
}

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器