JVM 字节码技术(多态的原理、异常处理和synchronized)

多态

使用一段简单的java代码实现多态用于后续测试

package Class_load.init_fun;
import java.io.IOException;
public class polymorphic {

        public static void test(Animal animal) {
            animal.eat();
            System.out.println(animal.toString());
        }

        public static void main(String[] args)  throws IOException {
            test(new Cat());
            test(new Dog());
            System.in.read();
        }
    }

    abstract class Animal {
        public abstract void eat();

        @Override
        public String toString() {
            return "我是" + this.getClass().getSimpleName();
        }
    }

    class Dog extends Animal {

        @Override
        public void eat() {
            System.out.println("啃骨头");
        }
    }

    class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("吃鱼");
        }
    }

在这里使用hsdb这个工具,

在这里插入图片描述
使用jps查看进程id,再在hsdb当中进行连接,运行Java代码,使用jps查看进程id,进行连接,在这里hsdb弹出了一个报错,如下图所示:

在这里插入图片描述
很显然表示在这个文件夹下面没有swaindbg.dll文件,这个文件又要去哪里找呢?

  • 首先我们找到jdk安装的路径(如果不记得了,可以再环境变量当中查看,java_home就是对应的位置)
  • 找到之后进入,在jre文件夹下的bin文件夹下就可以找到这个swaindbg.dll文件,如下图所示:

在这里插入图片描述

  • 最后一步就是把这个文件复制到报错信息当中的那个文件路径当中,
  • 再一次打开hsdb,连接一个进程id就不会出现报错了,

使用,在Tool当中打开Find Object by Query

在这里插入图片描述
弹出一个窗口,在这里使用的语法与sql语句相似,select ?from 数据类型 别名,如:select d from Class_load.init_fun.Dog d 执行这条语句,可以看到会出现这个对象所存储的地址,点击这个地址,会打开一个窗口,也就是对象在内存当中的实际表示:

在这里插入图片描述
查看内存所占的实际地址,打开Windows 下的 console,使用命令 mem + 地址 + 要查看几个地址。
mem 有两个参数,参数 1 是对象地址,参数 2 是查看 2 行

在这里插入图片描述
查看对象的完整的表示,在Tool下打开inspector,输入前面查询的地址,回车,

在这里插入图片描述

查看多态的方法

接着上面,在inspector当中有一个内存地址,在这里方法都存在vtable当中,vtable和这个地址之间相差1b8(16进制数)所以我们对前面的那个地址加上一个1b8即可进行查找,在我这里,inspector当中的地址现在是 0x0000000017914028 加上一个1b8 的值为1e0 ,之后进行console当中使用mem进行查看,在这里还没有获取到vtable的长度,在inspector当中的最后面可以查看。

在这里插入图片描述

这样之后我们就有了对应的地址和长度就可以进行查看了,并且打开Tool当中的Class Browser窗口,输入Dog类,会弹出Dog类对应的地址,点击进去,在这里可以发现,eat方法也正好对应了console当中查询出来的eat方法。

在这里插入图片描述
并且在这里我们对应这个Dog类的父类进行对比,可以发现toString方法是从Animal当中继承过来的,在这里所有类都继承至Object方法,在这里也就不进行测试Object方法了。

在这里插入图片描述

多态的调用步骤
  1. 先通过栈帧中的对象引用找到对象
  2. 分析对象头,找到对象的实际 Class
  3. Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
  4. 查表得到方法的具体地址
  5. 执行方法的字节码

异常处理

相同的,使用一段简单的java代码还进行测试,查看对应的字节码

package Class_load.init_fun;
public class try_catch {
    public static void main(String[] args) {
        int a = 0;
        try {
            a = 10;
        } catch (Exception e) {
            a = 30;
        }
    }
}

使用javap对class文件进行反编译,查看对应的字节码:

         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: goto          12
         8: astore_2
         9: bipush        30
        11: istore_1
        12: return
        
        Exception table:
         from    to  target type
             2     5     8   Class java/lang/Exception

  • 可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
  • 8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置

多个catch块的异常处理

package Class_load.init_fun;

public class many_catch {
    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (ArithmeticException e) {
            i = 30;
        } catch (NullPointerException e) {
            i = 40;
        } catch (Exception e) {
            i = 50;
        }
    }
}

同理获得反编译的字节码:因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用

         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: goto          26
         8: astore_2
         9: bipush        30
        11: istore_1
        12: goto          26
        15: astore_2
        16: bipush        40
        18: istore_1
        19: goto          26
        22: astore_2
        23: bipush        50
        25: istore_1
        26: return
      Exception table:
         from    to  target type
             2     5     8   Class java/lang/ArithmeticException
             2     5    15   Class java/lang/NullPointerException
             2     5    22   Class java/lang/Exception

multi-catch 的情况

在jdk1.7版本之后支持的一种编码方式,表示在这里的异常可以写在一起

package Class_load.init_fun;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class multi_catch {
    public static void main(String[] args) {
        try {
            Method test = multi_catch.class.getMethod("test");
            test.invoke(null);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public static void test() {
        System.out.println("ok");
    }
}

反编译后查看对应的字节码

         0: ldc           #2                  // class Class_load/init_fun/multi_catch
         2: ldc           #3                  // String test
         4: iconst_0
         5: anewarray     #4                  // class java/lang/Class
         8: invokevirtual #5                  // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
        11: astore_1
        12: aload_1
        13: aconst_null
        14: iconst_0
        15: anewarray     #6                  // class java/lang/Object
        18: invokevirtual #7                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        21: pop
        22: goto          30
        25: astore_1
        26: aload_1
        27: invokevirtual #11                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        30: return
      Exception table:
         from    to  target type
             0    22    25   Class java/lang/NoSuchMethodException
             0    22    25   Class java/lang/IllegalAccessException
             0    22    25   Class java/lang/reflect/InvocationTargetException

finally异常处理的情况

使用一段简单的带finally的代码进行测试

package Class_load.init_fun;

public class try_finally {
    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        } finally {
            i = 30;
        }
    }
}

对class进行反编译:

         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: bipush        30
         7: istore_1
         8: goto          27
        11: astore_2
        12: bipush        20
        14: istore_1
        15: bipush        30
        17: istore_1
        18: goto          27
        21: astore_3
        22: bipush        30
        24: istore_1
        25: aload_3
        26: athrow
        27: return
      Exception table:
         from    to  target type
             2     5    11   Class java/lang/Exception
             2     5    21   any
            11    15    21   any

可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程。

finally 当中出现return

如下代码所示:输出的结果是?

package Class_load.tryOrCatch;

public class finally_return {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
}

我们先查看字节码:

         0: bipush        10
         2: istore_0
         3: bipush        20
         5: ireturn
         6: astore_1
         7: bipush        20
         9: ireturn
      Exception table:
         from    to  target type
             0     3     6   any

输出的结果是20;

  • 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 的为准
  • 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常。

异常会被吞掉,我们在try块当中添加一条会抛出异常的语句,如 int j =1/0; 再一次运行代码。不会抛出异常,且打印的值还是20。

相同的,如果去掉finally当中的return,那输出的结果又是?

public class finally_not_return {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        int i = 10;
        try {
            return i;
        } finally {
            i = 20;
        }
    }
}

同样的先查看字节码:

         0: bipush        10
         2: istore_0
         3: iload_0
         4: istore_1		// 10 -> slot 1,暂存至 slot 1,目的是为了固定返回值
         5: bipush        20
         7: istore_0		// 20 -> i
         8: iload_1			// <- slot 1(10) 载入 slot 1 暂存的值
         9: ireturn			// 返回栈顶的 int(10)
        10: astore_2
        11: bipush        20
        13: istore_0
        14: aload_2
        15: athrow
      Exception table:
         from    to  target type
             3     5    10   any

所以在这里返回的是10,

synchronized块

在Java代码当中synchronized关键字表示每个代码块是要一起执行的,也就加锁操作。

使用一段简单的java加锁代码:

public class synchronized_test {
    public static void main(String[] args) {
        Object lock = new Object();
        synchronized(lock){
            System.out.println("ok");
        }
    }
}

同样的,对class文件进行反编译:查看对应的字节码和异常表

         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter				//加锁
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String ok
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2
        21: monitorexit					//解锁
        22: goto          30
        25: astore_3
        26: aload_2
        27: monitorexit					//有异常抛出同样会解锁
        28: aload_3
        29: athrow
        30: return
      Exception table:
         from    to  target type
            12    22    25   any
            25    28    25   any

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Modify_QmQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值