字节码指令集与解析

思维导图

在这里插入图片描述

01概述

在这里插入图片描述

执行模型

在这里插入图片描述

字节码与数据类型

在这里插入图片描述
在这里插入图片描述

指令分析

在这里插入图片描述

02-加载与存储指令

在这里插入图片描述
在这里插入图片描述

复习:再谈操作数栈与局部变量表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1-局部变量压栈指令

在这里插入图片描述
举例分析如下:
在这里插入图片描述
在这里插入图片描述

2-常量入栈指令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
举例解释如下:
在这里插入图片描述
在这里插入图片描述

注意:常量入栈指令中的n和局部变量压栈指令中的n不一样,本次的n代表数值或者对象,而不是局部变量表中的下标

3-出栈入局部变量表指令

在这里插入图片描述
举例分析如下:
在这里插入图片描述
里面有代码,也有字节码,所以可以根据老师给的图展开分析,
首先该方法被调用的时候,形式参数k和d都是有确定的值,由于该方法不是静态方法,所以局部变量表中的第一个位置(槽位)存储this,而第二个位置存储k具体的值,由于老师只是分析,没有调用这个方法,所以老师全部使用的变量名称来代替具体的值,所以明白就好,继续来分析,然后第三个和第四个位置储存d具体的值,由于d是double类型,所以需要占据两个槽位,数据已经准备好了,那就来看字节码,首先iload_1是将局部变量表中下标为1的k值取出来压入操作数栈中,然后iconst_2是将常量池中的整型值2压入操作数栈,iadd让操作数栈弹出的k值和整型值2执行相加操作,之后将相加的结果值m压入操作数栈中,

请注意老师的画法,在执行弹栈和压栈操作之后,老师并没有删除操作数栈中的k值和2,这是因为老师让我们知道具体的操作过程,所以故意为之,不过真正的操作是弹栈之后k值和2就会从操作数栈中弹出,之后操作数栈中就没有k值和2了,只有m值了,然后istore_4是将操作数栈中的m值弹出栈,然后放在局部变量表中下标为4的位置,idc2_w #13<12>代表将long型值12压入操作数栈,istore5是将值12弹栈之后放入局部变量表中下标为5的位置,由于12是long型,所以占据两个位置(槽位),ldc #15<atguigu>代表将字符串atguigu压入操作数栈,astore 7代表将字符串atguigu弹栈之后放入局部变量表中下标为7的位置,idc #16<10.0>代表将float类型数据10.0压入操作数栈,fstore 8代表将10.0弹出栈,然后放入局部变量表中下标为8的位置,idc2_w #17<10.0>代表将10.0压入操作数栈,dstore2代表将10.0弹出栈,之后将10.0放入下标为2和3的操作,毕竟这是double类型数据

槽位复用:
在这里插入图片描述

注意:在方法没有运行的时候,根据字节码文件就可以计算出需要几个槽位

/**
 * @author zhangjianbin
 * @date 2021年07月18日21:46
 */
public class LoadAndStoreTest {

    //1.局部变量压栈指令
    public void load(int num, Object obj, long count, boolean flag, short[] arr) {
        System.out.println(num);
        System.out.println(obj);
        System.out.println(count);
        System.out.println(flag);
        System.out.println(arr);
    }

    //2.常量入栈指令
    public void pushConstLdc() {
        int i = -1;
        int a = 5;
        int b = 6;
        int c = 127;
        int d = 128;
        int e = 32767;
        int f = 32768;
    }

    public void constLdc() {
        long a1 = 1;
        long a2 = 2;
        float b1 = 2;
        float b2 = 3;
        double c1 = 1;
        double c2 = 2;
        Date d = null;
    }

    // 3. 出栈装入局部变量表指令
    public void store(int k, double d) {
        int m = k + 2;
        long l = 12;
        String str = "hello";
        float f = 10.0F;
        d = 10;
    }

    public void foo(long l, float f) {
        {
            int i = 0;
        }
        {
            String s = "hello";
        }
    }
}

03-算数指令

在这里插入图片描述
在这里插入图片描述
对于无穷大和NaN的举例:
在这里插入图片描述
所有算术指令
在这里插入图片描述
举例
在这里插入图片描述
在这里插入图片描述
一个曾经的案例1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一个曾经的案例2
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:println()方法的本地变量表中会放55,这样该方法就可以使用了

比较指令的说明

在这里插入图片描述
注意:NaN(Not a Number)表示不是一个数字,比如0.0/0.0得到的可能是1.0(两个数相等),也可能是0.0(0.0是分子),也可能是无穷大(0.0是分母),所以老师给出的解释是NaN代表无法确定是什么数字,只有double和float类型中可能出现NaN的情况,而long类型不会出现NaN,所以只有lcmp,而没有lcml

/**
 * @author zhangjianbin
 * @date 2021年07月18日22:29
 */
public class ArithmeticTest {
    public void method1() {
        int i = 10;
        double j = i / 0.0;
        System.out.println(j);//无穷大 Infinity

        double d1 = 0.00;
        double d2 = d1 / 0.0;
        System.out.println(d2); //Nan :不是一个确定的数值

    }

    public void method2() {
        float i = 10;
        float j = -1;
        i = -j;
    }

    public void method3(int j) {
        int i = 100;
        i = i + 10;
        i += 10;
    }

    public int method4() {
        int a = 80;
        int b = 7;
        int c = 10;
        return (a + b) * c;
    }

    public int method5(int i, int j) {
        return ((i + j - 1) & ~(j - 1));
    }

    // i++ 和 ++i
    public void method6() {
        int i = 10;
        i++;
        //++i;
    }

    public void method7() {
        int i = 10;
        int a = i++;


        int j = 20;
        int b = ++j;
    }

    public void method8() {
        int i = 10;
        i = i++;
        System.out.println(i);//10
    }
}

04-类型转换指令

在这里插入图片描述
1-宽化类型转换
在这里插入图片描述
在这里插入图片描述
2-窄化类型转换
在这里插入图片描述

注意:从float、double、long等类型往byte、short、char类型转换的时候,需要先把前面几种类型转换成int类型,然后在从int类型转换到后面这几种类型,所以int类型相等于一种过渡类型

在这里插入图片描述

/**
 * @author zhangjianbin
 * @date 2021年07月19日21:35
 */
public class ClassCastTest {

    // 针对于宽化类型转换的基本测试
    public void upCast1() {
        int i = 10;
        long l = i;
        float f = i;
        double d = i;

        float f1 = l;
        double d1 = l;

        double d2 = f1;
    }

    // 举例 精度损失的问题
    public void upCast2() {
        int i = 123123123;

        float f = i;

        System.out.println(f);

        Long l = 1212121212L;

        l = 121212121212121212L;

        double d = l;

        System.out.println(d);
    }

    // 针对于 byte short等转换为容量大的类型
    public void upCast3(byte b) {
        int i = b;
        long l = b;
        double d = b;
    }

    public void upCast4(short s) {
        int i = s;
        long l = s;
        float f = s;
    }


    // 窄化类型转换
    public void downCast1() {
        int i = 10;
        byte b = (byte) i;

        short s = (short) i;
        char c = (char) i;


        long l = 10L;
        int i1 = (int) l;
        byte b1 = (byte) l;
    }

    public void downCast2() {
        float f = 10;
        long l = (long) f;

    }

    public void downCast3() {
        short s = 10;
        byte b = (byte) s;
    }

    public void downCast4() {
        int i = 128;
        byte b = (byte) i;
        System.out.println(b);// -128
    }

    public void downCast5() {
        double d1 = Double.NaN; // 表示不确定的值  例如 0.0/0.0

        int i = (int) d1;
        System.out.println(d1); // NaN
        System.out.println(i);//0

        double d2 = Double.POSITIVE_INFINITY; // 正无穷大

        long l = (long) d2;
        System.out.println(l);
        System.out.println(Long.MAX_VALUE);
        int j = (int) d2;
        System.out.println(j);
        System.out.println(Integer.MAX_VALUE);

        float f = (float) d2;
        System.out.println(f);

    }
}

05-对象的创建与访问指令

在这里插入图片描述
1-创建指令
在这里插入图片描述
2-字段访问指令

在这里插入图片描述
在这里插入图片描述

注意:get***是入栈,而put***是出栈

3-数组操作指令
在这里插入图片描述
在这里插入图片描述
4-类型检查指令
在这里插入图片描述


/**
 * @author zhangjianbin
 * @date 2021年07月20日20:23
 */
public class NewTest {
    // 1 创建指令
    public void newInstance() {
        Object obj = new Object();
        System.out.println(obj);

        File file = new File("hello");
    }

    public void newArray() {
        int[] intArray = new int[10];
        Object[] objArray = new Object[10];
        int[][] mintArray = new int[10][10];
        String[][] strArray = new String[10][];
    }

    //字段访问指令
    public void sayHello() {
        System.out.println("hello");
    }

    public void setOrderId() {
        Order order = new Order();
        order.id = 101;
        System.out.println(order.id);

        Order.name = "order";
        System.out.println(Order.name);
    }

    private static class Order {
        public static String name;
        public int id;
    }

    // 数组操作指令
    public void setArray() {
        int[] intArray = new int[10];
        intArray[3] = 20;
        System.out.println(intArray[1]);

        boolean[] arr = new boolean[10];
        arr[1] = true;
    }

    public void arrLength() {
        double[] arr = new double[10];
        System.out.println(arr.length);
    }

    public String checkCast(Object obj) {
        if (obj instanceof String) {
            return (String) obj;
        } else {
            return null;
        }
    }
}

06-方法调用与返回指令

1-方法调用指令
在这里插入图片描述
注意:
1、invokedynamic老师不讲,估计是很少遇到吧
2、invokeinterface是对接口而言的,用属于接口类型的对象调用方法的时候就是这个
3、invokespecial只有构造器、私有方法、super.方法名()调用父类方法这几种情况,其中调用父类方法这种情况可能出现其直接父类没有该方法,那就可以调用其父类继承的父类中的该方法,最终找到一个方法调用就是了
4、invokestatic是调用static静态方法,无论是使用对象.静态方法名()还是类名.静态方法名()都是invokestatic,也不难理解
5、invokevirtual是调用类中的非静态普通方法,而这种实例方法可能调用的是子类重写的非静态普通方法,比如A a = new B();a.hello(),其中B类继承A类,并且B类重写了A类中的hello()方法,这种情况下就是invokevirtual了,但是有可能该类没有子类,调用的就是本类中的非静态普通方法,这种情况也是invokevirtual了

2-方法返回指令
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


/**
 * @author zhangjianbin
 * @date 2021年07月20日21:19
 */
public class MethodInvokeReturnTest {
    //方法调用指令 invokespecial  静态分派
    public void invoke1() {
        // 情况1 类实例构造器方法 <init>()
        Date date = new Date();

        Thread t1 = new Thread();
        // 情况2 父类的方法
        super.toString();

        // 情况3 私有方法
        methodPrivate();
    }

    private void methodPrivate() {
    }

    // 方法调用指令 invokestatic 静态分派
    public void invoke2() {
        methodStatic();
    }

    public static void methodStatic() {

    }

    private static void methodStatic2() {

    }

    // 方法调用指令 invokeinterface
    public void invoke3() {
        Thread t1 = new Thread();
        ((Runnable) t1).run();

        Comparable<Integer> com = null;
        com.compareTo(123);
    }

    // 方法调用指令 invokeVirtual  动态分派
    public void invoke4() {
        System.out.println("hello");

        Thread t1 = null;
        t1.run();
    }

    // 方法的返回指令
    public int returnInt() {
        int i = 500;
        return i;
    }

    public double returnDouble() {
        return 0.0;
    }


    public String returnString() {
        return "hello";
    }

    public int[] returnArr() {
        return null;
    }

    public float returnFloat() {
        float f = 2.9F;
        return f;
    }

    public byte returnByte() {
        return 0;
    }
}

07-操作数栈管理指令

在这里插入图片描述
在这里插入图片描述

08-控制转义指令

在这里插入图片描述
1-条件跳转指令
在这里插入图片描述
在这里插入图片描述
注意:
1、对于float、double、long类型的比较,它们比较之后生成的是int类型的0、1、-1,这个过程可以使用比较指令和条件跳转指令来完成,虽然得到的是int类型的值,但是System.out.println(XXX)中的值是布尔类型,你可以在jclasslib中的常量池信息中看到写的是Z,代表布尔值类型
2、int类型值(包含byte、char、short)比较 和 对象类型值比较需要使用比较条件跳转指令

2-比较条件跳转指令
在这里插入图片描述
注意:
1、上面所说的后者是栈顶元素,而前者是栈顶下面的元素
2、对于float、double、long类型的比较,它们比较之后生成的是int类型的0、1、-1,这个过程可以使用比较指令和条件跳转指令来完成
而 int类型值(包含byte、char、short)比较 和 对象类型值比较需要使用比较条件跳转指令,其中对象类型值不是比较的地址,就是比较对象中的某些字段值,这又归咎到float、double、long、int类型的比较中了
3、无论哪种比较,也不管两个比较值的中间是什么符号(>、<、>=、<=等等),始终都是栈顶下部元素 比较符 栈顶元素,这是不会改变的,然后结合比较符得出结果,如果是true,那就跳转,否则不跳转继续往下执行

3-多条件分支跳转
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4-无条件跳转
在这里插入图片描述

09-异常处理指令

1-抛出异常指令
在这里插入图片描述
注意:
如果使用throw new 异常名称()这种形式来抛出异常,那就会在代码中出现athrow指令,而在方法上面添加throw 异常名称这种形式来抛出异常,然后使用jclasslib的时候就会出现在方法下面多出现一个属性Exceptions,如下图所示:
在这里插入图片描述
2-异常处理与异常表
在这里插入图片描述
异常表如下所示:
在这里插入图片描述
异常表的含义是如果在Start PC和End PC之间(大于等于Start PC,小于End PC)出现对应的Catch Type异常问题(出现异常就匹配对应的异常),将会在操作数栈中压入相应的异常类对象,之后跳转到Handler PC的位置去执行对应的字节码指令
注意:
当异常出现的时候也会压入操作数栈,之后还会存储局部变量表中

10-同步控制指令

在这里插入图片描述
1-方法级的同步
同步方法(添加synchronized的方法):
在这里插入图片描述
在这里插入图片描述
注意:
一个方法无论是否添加synchronized,你都无法在字节码中看出区别,例如:
在这里插入图片描述

是否是同步方法在字节码文件中你是无法看出区别的,但是可以在方法访问标识中看出区别

2-方法内指令指令序列的同步
同步代码块:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例子:
在这里插入图片描述
操作数栈中的对象和monitorenter结合起来可以让线程获取锁,做法就是让对象的监视器标记从0变成1,这就代表该线程上锁了,然后在操作数栈的aload_1monitorexit结合起来就可以让线程解锁,做法就是让对象的监视器标记从1变成0,这个解锁需要在方法退出之前完成,如果方法执行过程中出现了任何异常,将会跳到异常处理的字节码处执行相关代码,如果异常处理的字节码部分出现了问题,那就重新执行异常处理的字节码,这些内容都在异常表中写的很明确,其中异常表也在上面截图中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值