1.操作数栈

解释时,JVM会为方法分配一个栈帧,而栈帧又由 局部变量表,操作数帧,方法引用,动态链接 组成

方法中的每条指令执行时,要求该指令的操作数已经压入栈中;执行指令时会将操作数从栈中弹出,是否将操作数再次压入栈中取决与具体的命令。

new,dup指令

使用new关键字创建对象的时候出现的字节码指令,通常伴随着 dup 指令 ,dup指令将复制一份操作数栈顶的值

这里是因为 invokespecial 调用类的构造方法时,将会消耗new的结果引用,如果我们不复制一份 ,那么这个引用就丢掉了。

public class ByteCodeDemo {

    public static void main(String[] args) {

        Object b = new Object();
    }
}

0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init> : ()V>
7 astore_1
8 return

pop 指令

    public static void main(String[] args) throws InterruptedException {
        ByteCodeDemo byteCodeDemo = new ByteCodeDemo();
        byteCodeDemo.methodOne();

    }

    public  String methodOne(){
        return "no";
    }
  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    Code:
       0: new           #2                  // class com/sz/jasyptdemo/ByteCodeDemo
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method methodOne:()Ljava/lang/String;
      12: pop
      13: return

调用了methodOne()方法,这个方法是有返回结果的,但是我们并没有接受 因此JVM会调用 pop指令,将返回值抛弃掉

iconst,bipush,sipush,ldc

iconst 表示加载一个常量,常量的值范围在 -1 ~5 之间,bipush 加载一个字节所能表示的int值,sipush加载两个字节所能表示的int值,ldc 则能加载任意值

    public static void main(String[] args) throws InterruptedException {
        int a = 5;
        int b = 6;
        int c = 129;
        int d = 32768;
    }
         0: iconst_5
         1: istore_1
         2: bipush        6
         4: istore_2
         5: sipush        129
         8: istore_3
         9: ldc           #2                  // int 32768
        11: istore        4
        13: return

《深入理解Java虚拟机》 JAVA 字节码指令 基础-LMLPHP

Throwable

如果抛出异常,会将操作数栈清空,然后将异常实例压入操作数栈

2. 局部变量表 (数组)

加载与存储 load,store

Java 方法栈桢的另外一个重要组成部分则是局部变量区,字节码程序可以将计算的结果缓存在局部变量区之中。

Java 虚拟机将局部变量区当成一个数组,如果是实例方法,那么局部变量表这个数组的0号下标位置就是就是this指针,1号下标位置就是 参数,后面依次存放局部变量。

    public static void main(String[] args) throws InterruptedException {
        ByteCodeDemo byteCodeDemo = new ByteCodeDemo();
        byteCodeDemo.method(3);
    }

    public void method(int i){
        int a = 5;
        int b = 6;
        int c = 129;
        int d = 32768;
    }

对应的局部变量表

《深入理解Java虚拟机》 JAVA 字节码指令 基础-LMLPHP

因为调用的是实例方法,所以本地变量表序号0的位置上是 this指针,1号上是 方法参数 i,后面依次是方法从上往下的局部变量。

JVM对局部变量的主要有两组命令 加载 命令 load, 存储命令 sotre

   public static void main(String[] args) throws InterruptedException {
        ByteCodeDemo byteCodeDemo = new ByteCodeDemo();
        byteCodeDemo.method(3);
    }

    public void method(int i){
        int a = 5;
        int b = 6;
        int c = 129;
        int d = 32768;

        if (d<300){
            System.out.println("...");
        }
    }
 0 iconst_5
 1 istore_2
 2 bipush 6
 4 istore_3
 5 sipush 129
 8 istore 4
10 ldc #5 <32768>
12 istore 5
14 iload 5
16 sipush 300
19 if_icmpge 30 (+11)
22 getstatic #6 <java/lang/System.out : Ljava/io/PrintStream;>
25 ldc #7 <...>
27 invokevirtual #8 <java/io/PrintStream.println : (Ljava/lang/String;)V>
30 return

如上的字节码指令所示,首先是加载常量5到栈顶,然后调用 istore 将栈顶元素存储到局部变量表下标为2的位置上;然后调用bispush,将6压到栈顶… 然后调用iload指令 加载局部变量表下标为5的内容,与栈顶元素 300 比较,如果满足条件,往下执行;如果不满足,跳到偏移量为30的地方执行return返回;

不同基本数据的类型,有不同的load和store命令,如下图所示

《深入理解Java虚拟机》 JAVA 字节码指令 基础-LMLPHP

通常对局部变量的操作是首先加载值然后压到操作数栈中进行计算,如下所示

    public void method(int i){
        int a = 5;
        int b = a+10;
        int c = 129;
        int d = 32768;


        if (d<300){
            System.out.println("...");
        }
    }
 0 iconst_5
 1 istore_2
 2 iload_2
 3 bipush 10
 5 iadd
 6 istore_3
 7 sipush 129
10 istore 4
12 ldc #5 <32768>
14 istore 5
16 iload 5
18 sipush 300
21 if_icmpge 32 (+11)
24 getstatic #6 <java/lang/System.out : Ljava/io/PrintStream;>
27 ldc #7 <...>
29 invokevirtual #8 <java/io/PrintStream.println : (Ljava/lang/String;)V>
32 return

如第2,3,5行指令所示;

但是也有指令能直接对局部变量表上的数值进行运算: 自增,自减操作

直接操作局部变量表的指令 iinc

    public void method(int i){
        int a = 1;
        int b = 10;

        a++;
        b--;
    }
 0 iconst_1
 1 istore_2
 2 bipush 10
 4 istore_3
 5 iinc 2 by 1
 8 iinc 3 by -1
11 return

如上字节码所示 iinc + 局部变量表的下标 + by + 增加的值

3. 其他指令

instanceof

后跟目标类,判断栈顶元素是否为目标类 / 接口的实例。是则压入 1,否则压入 0

    public void method(int i){
        Date date = new Date();
        if (date instanceof Object) {
            System.out.println("ofcause");
        }
    }
 0 new #5 <java/util/Date>
 3 dup
 4 invokespecial #6 <java/util/Date.<init> : ()V>
 7 astore_2
 8 aload_2
 9 instanceof #7 <java/lang/Object>
12 ifeq 23 (+11)
15 getstatic #8 <java/lang/System.out : Ljava/io/PrintStream;>
18 ldc #9 <ofcause>
20 invokevirtual #10 <java/io/PrintStream.println : (Ljava/lang/String;)V>
23 return

getstatic

访问静态字段,见上

monitorente monitorexit

为栈顶元素加锁和解锁

    public void method(int i){
      synchronized (new Object()){
          System.out.println("sync");
      }
    }

 0 new #5 <java/lang/Object>
 3 dup
 4 invokespecial #1 <java/lang/Object.<init> : ()V>
 7 dup
 8 astore_2
 9 monitorenter
10 getstatic #6 <java/lang/System.out : Ljava/io/PrintStream;>
13 ldc #7 <sync>
15 invokevirtual #8 <java/io/PrintStream.println : (Ljava/lang/String;)V>
18 aload_2
19 monitorexit
20 goto 28 (+8)
23 astore_3
24 aload_2
25 monitorexit
26 aload_3
27 athrow
28 return
05-19 00:56