JVM面试题以及字节码讲解

7 篇文章 0 订阅

0 前言

关于java字节码指令的含义,可以参考这篇博客:Java字节码指令含义解释与指令查询

1 try…catch…finally

1.1 catch

1.1.1 catch单个异常

查看相应的java代码:

public class FinallyDemo {
    public static void main(String[] args) {
        int i = 10;
        try{
            i = 20;
        }catch (NegativeArraySizeException e){
            i = 30;
        }
    }
}

接下来通过javap -v xxx.class命令,来查看该段源码的字节码反编译之后的信息:

{ //源代码对应的字节码指令
  public com.basic.FinallyDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/basic/FinallyDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: bipush        10  //将10压入栈顶
         2: istore_1          //将10存储到slot 1中,即10 -> i
         3: bipush        20  //将20压入栈顶, try-----开始
         5: istore_1          //将20存储到slot 1中,即20 -> i  try------结束
         6: goto          13  //这里没有判断条件,但是有一个goto指令,结合异常表一起执行
         9: astore_2     //存储异常信息到slot 2,即 异常信息 -> e
        10: bipush        30  //将30压入栈顶,
        12: istore_1          //将30存储到slot 1中,即 30 -> i
        13: return            //返回栈顶元素
      Exception table:  //被try..catch包裹的代码,会产生一个异常表
         from    to  target type
             3     6     9   Class java/lang/NegativeArraySizeException //监测try中的代码,如果发生异常,跳转到第9行
      LineNumberTable:
        line 5: 0
        line 7: 3
        line 10: 6
        line 8: 9
        line 9: 10
        line 11: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10       3     2     e   Ljava/lang/NegativeArraySizeException;
            0      14     0  args   [Ljava/lang/String;
            3      11     1     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/NegativeArraySizeException ]
        frame_type = 3 /* same */
}
SourceFile: "FinallyDemo.java"

从反编译之后的字节码信息可以看到,之所以能执行catch中的代码,是因为有一个异常表(exception table)来存放trycatch的执行的起始位置,当检测到try中有异常发生时,跳转到catch中执行。

1.1.2 catch多个异常

先看java源代码:

import java.nio.file.FileSystemAlreadyExistsException;

public class FinallyDemo {
    public static void main(String[] args) {
        int i = 10;
        try{
            i = 20;
        }catch (NegativeArraySizeException e){
            i = 30;
        }catch (NullPointerException e){
            i = 40;
        }catch (FileSystemAlreadyExistsException e){
            i = 50;
        }
        System.out.println(i);
    }
}

源代码对应的字节码指令:

{
  public com.basic.FinallyDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/basic/FinallyDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: bipush        10  //定义变量i,将10压入栈顶
         2: istore_1
         3: bipush        20  //try------开始
         5: istore_1          //try------结束
         6: goto          27
         9: astore_2          //catch1-----开始
        10: bipush        30
        12: istore_1          //catch2----结束
        13: goto          27
        16: astore_2   //catch2-----开始
        17: bipush        40
        19: istore_1   //catch2-----结束
        20: goto          27
        23: astore_2    //catch3-----开始
        24: bipush        50
        26: istore_1    //catch3------结束
        27: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: iload_1
        31: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        34: return
      Exception table:  //异常表中有三个异常,都是监测try中的代码
         from    to  target type
             3     6     9   Class java/lang/NegativeArraySizeException
             3     6    16   Class java/lang/NullPointerException
             3     6    23   Class java/nio/file/FileSystemAlreadyExistsException
      LineNumberTable:
        line 8: 0
        line 10: 3
        line 17: 6
        line 11: 9
        line 12: 10
        line 17: 13
        line 13: 16
        line 14: 17
        line 17: 20
        line 15: 23
        line 16: 24
        line 18: 27
        line 19: 34
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10       3     2     e   Ljava/lang/NegativeArraySizeException;
           17       3     2     e   Ljava/lang/NullPointerException;
           24       3     2     e   Ljava/nio/file/FileSystemAlreadyExistsException;
            0      35     0  args   [Ljava/lang/String;
            3      32     1     i   I
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/NegativeArraySizeException ]
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/NullPointerException ]
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/nio/file/FileSystemAlreadyExistsException ]
        frame_type = 3 /* same */
}
1.1.3 catch多个异常的简写

这是上一小节中的catch多个异常的简写形式:

import java.nio.file.FileSystemAlreadyExistsException;

public class FinallyDemo {
    public static void main(String[] args) {
        int i = 10;
        try{
            i = 20;
        }catch (NegativeArraySizeException | NullPointerException | FileSystemAlreadyExistsException e){
            i = 30;
        }
        System.out.println(i);
    }
}

对应的字节码信息和上一节中的相同:

{
  public com.basic.FinallyDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/basic/FinallyDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_1
         6: goto          13
         9: astore_2
        10: bipush        30
        12: istore_1
        13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_1
        17: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        20: return
      Exception table:
         from    to  target type
             3     6     9   Class java/lang/NegativeArraySizeException
             3     6     9   Class java/lang/NullPointerException
             3     6     9   Class java/nio/file/FileSystemAlreadyExistsException
      LineNumberTable:
        line 8: 0
        line 10: 3
        line 13: 6
        line 11: 9
        line 12: 10
        line 14: 13
        line 15: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10       3     2     e   Ljava/lang/RuntimeException;
            0      21     0  args   [Ljava/lang/String;
            3      18     1     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/RuntimeException ]
        frame_type = 3 /* same */
}

1.2 finally

1.2.1 在finally中return *;

查看FinallyDemo.java文件的代码,试问最终输出多少?10? or 20? or 30?

FinallyDemo.java

public class FinallyDemo {
    public static void main(String[] args) {
        FinallyDemo finallyDemo = new FinallyDemo();
        int t = finallyDemo.test();
        System.out.println(t);
    }

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

运行结果:

30

通过对其FinallyDemo.class文件进行反编译,可以分析得到为什么最终结果会是30。为了简化代码,这里就直接将源代码对应的字节码信息粘贴过来。

FinallyDemo.class

{
 public com.basic.FinallyDemo();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=1, locals=1, args_size=1
        0: aload_0
        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
        4: return
     LineNumberTable:
       line 3: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0       5     0  this   Lcom/basic/FinallyDemo;

 public static void main(java.lang.String[]);
   descriptor: ([Ljava/lang/String;)V
   flags: ACC_PUBLIC, ACC_STATIC
   Code:
     stack=2, locals=3, args_size=1
        0: new           #2   // class com/basic/FinallyDemo  在堆中创建对象
        3: dup
        4: invokespecial #3   // Method "<init>":()V 调用FinallyDemo构造方法
        7: astore_1    //  将新创建的对象存储到局部变量slot 1中
        8: aload_1   //加载局部变量slot 1中的对象
        9: invokevirtual #4   // Method test:()I   //执行slot 1中的test()方法 
       12: istore_2   //将test()返回的int类型存储到int类型的局部变量slot 2中
       13: getstatic     #5 // Field java/lang/System.out:Ljava/io/PrintStream; 获得静态输出方法
       16: iload_2  //将在需要打印的变量 slot 2
       17: invokevirtual #6  // Method java/io/PrintStream.println:(I)V 调用静态对象的println方法,并调用需要打印的变量
       20: return
     LineNumberTable:
       line 5: 0
       line 6: 8
       line 7: 13
       line 8: 20
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      21     0  args   [Ljava/lang/String;
           8      13     1 finallyDemo   Lcom/basic/FinallyDemo;
          13       8     2     t   I

 public int test();
   descriptor: ()I
   flags: ACC_PUBLIC
   Code:
     stack=1, locals=4, args_size=1
        0: bipush        10  //将10压入栈顶
        2: istore_1          // 10 -> i  
        3: bipush        20  //在try中------,  将20压入栈顶
        5: istore_2          //20 -> slot 2 该局部变量是一个隐藏的局部变量,LocalVariableTable不显示
        6: bipush        30  //将30压入栈顶
        8: ireturn        //----try结束  返回栈顶元素  30
        9: astore_3       //在finally中-------
       10: bipush        30 //将30压入栈顶
       12: ireturn        //finally------结束,返回栈顶元素
     Exception table:
        from    to  target type
            3     6     9   any
     LineNumberTable:
       line 11: 0
       line 13: 3
       line 15: 6
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      13     0  this   Lcom/basic/FinallyDemo;
           3      10     1     i   I
     StackMapTable: number_of_entries = 1
       frame_type = 255 /* full_frame */
         offset_delta = 9
         locals = [ class com/basic/FinallyDemo, int ]
         stack = [ class java/lang/Throwable ]
}
SourceFile: "FinallyDemo.java"
  • 分析:

    我们从反编译之后的字节码可以看出,之所以finally中的代码最后都会被执行,是因为在字节码中,将finally中的代码复制了一份,所以才会总是执行。

1.2.2 在finally中使用return的弊端

使用try..catch捕捉程序异常,但是有些时候,catch不能将所有的异常都捕捉到,此时那些没有被catch捕捉到的异常就会在finally中被再次捕捉到,这样就提高了程序运行的安全性。但是当在finally中使用return语句后,那么finally就会将其他的异常”吞掉“,也就是不再捕捉catch中没有被捕捉的异常。

1.2.2.1 finally中没有加return语句:
public class FinallyDemo {
    public static void main(String[] args) {
        int i = 10;
        try{
            i = 20;
        }catch (NegativeArraySizeException e){
            e.printStackTrace();
        }finally {
            i = 30;
        }
    }
}

为了简便,这里主要粘贴源代码对应的字节码信息:

{
  public com.basic.FinallyDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/basic/FinallyDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: bipush        10 //将10压入栈顶,全部变量
         2: istore_1         //将10存储到slot 1中,10 -> i
         3: bipush        20  //try-----开始, 将20压入栈顶
         5: istore_1          //将20存储到slot 1中, 20 -> i
         6: bipush        30  //将30压入栈顶,
         8: istore_1          //将30存储到slot 1中,30 -> i  try----结束
         9: goto          29  
        12: astore_2        //catch------开始
        13: aload_2
        14: invokevirtual #3                  // Method java/lang/NegativeArraySizeException.printStackTrace:()V
        17: bipush        30
        19: istore_1        //catch------结束
        20: goto          29
        23: astore_3     //finally----开始
        24: bipush        30
        26: istore_1
        27: aload_3
        28: athrow   //finally------结束,抛出在finally里中捕捉到的异常
        29: return
      Exception table: //异常表,存放异常的跳转的起始位置,左闭右开区间,以第一个为例,从第3行到第5行
         from    to  target type
             3     6    12   Class java/lang/NegativeArraySizeException //监测try中的代码
             3     6    23   any   //finally在try中的跳转
            12    17    23   any   //finally在catch中的跳转
      LineNumberTable:
        line 5: 0
        line 7: 3
        line 11: 6
        line 12: 9
        line 8: 12
        line 9: 13
        line 11: 17
        line 12: 20
        line 11: 23
        line 12: 27
        line 13: 29
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       4     2     e   Ljava/lang/NegativeArraySizeException;
            0      30     0  args   [Ljava/lang/String;
            3      27     1     i   I
      StackMapTable: number_of_entries = 3
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/NegativeArraySizeException ]
        frame_type = 74 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 5 /* same */
}
1.2.2.2 finally中加了return语句
public class FinallyDemo {
    public static void main(String[] args) {
        int j = test();
        System.out.println(j);
    }

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

源代码对应的字节码:

{
  public com.basic.FinallyDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/basic/FinallyDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokestatic  #2                  // Method test:()I
         3: istore_1
         4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: iload_1
         8: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        11: return
      LineNumberTable:
        line 8: 0
        line 9: 4
        line 10: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0  args   [Ljava/lang/String;
            4       8     1     j   I

  public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=0
         0: iconst_0    //定义变量i
         1: istore_0    
         2: bipush        10   //try-----开始
         4: istore_1           
         5: bipush        20
         7: ireturn            //try-----结束
         8: astore_2           //finally---开始
         9: bipush        20   //   finally中并没有捕捉异常,也没有抛出异常
        11: ireturn            //finally---结束
      Exception table:
         from    to  target type
             2     5     8   any
      LineNumberTable:
        line 13: 0
        line 15: 2
        line 17: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2      10     0     i   I
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 8
          locals = [ int ]
          stack = [ class java/lang/Throwable ]
}

我们从字节码中可以看到,finally中并没有捕捉异常,也没有抛出异常,所以此种写法对于程序而言非常危险。下面看一段错误代码,但是程序并没有抛出异常:

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

    public static int test(){
        int i = 1;
        try{
            i = i / 0;  //此处是一个异常代码
            return i;
        }finally {
            return i;
        }
    }
}

运行结果:

程序没有抛出异常

所以,在写代码时,一定要主要不要写出这样的代码。

写在最后

欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。
微信公众号

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值