Java 字节码阅读

作者: 魏树鑫 | 来源:发表于2019-05-16 20:33 被阅读1次

    很多时候我们通过代码无法了解字节码执行过程,比如Try Catch Finally的执行过程,只有通过debug或者阅读字节码才能搞懂JVM是如何编译和设计执行流程的。

    public class TestTryCatch {
        private String feild = "this is class variables";
        public static String staticFeild = "this is class static variables";
        public static final String staticFinalFeild = "this is class static final variables";
        public volatile String volatileFeild = "this is class volatile variables";
    
        static {
            System.out.println("from the class static block");
        }
    
        {
            System.out.println("from the class  block");
        }
    
        public TestTryCatch() {
        }
    
        public TestTryCatch(String feild) {
            this.feild = feild;
        }
    
        public static String staticMethod() {
            int a = 1;
            int b = 10;
            a = b;
            return "from static method";
        }
    
        public synchronized void syncMethod() {
            System.out.println("from sync method ");
        }
    
        public void syncBlock() {
            synchronized (this) {
                System.out.println("from sync block");
            }
        }
    
        public String main(String[] args) {
            try {
                staticMethod();
                syncBlock();
                return "from try";
            } catch (Exception e) {
                e.printStackTrace();
                return "from catch";
            } finally {
                return "from finally";
            }
        }
    }
    

    可以通过$JDKPath$\bin\javap -c -verbose $FileClass$来编译Java文件,也可以看我的另一篇博客,通过IDEA来查看https://www.jianshu.com/p/738de4e519b6
    编译成字节码之后:
    分析在后面,比较长,可以直接划过,回头再看

    "/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home\bin\javap" -c -verbose com.wei.sample.clazz.TestTryCatch
    Classfile /Users/shuxin.wei/Documents/icourtCode/sample/app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/wei/sample/clazz/TestTryCatch.class
      Last modified 2019-5-16; size 1901 bytes
      MD5 checksum 00ed76899d96d428782d4d128839aa9f
      Compiled from "TestTryCatch.java"
    public class com.wei.sample.clazz.TestTryCatch
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #22.#59        // java/lang/Object."<init>":()V
       #2 = String             #60            // this is class variables
       #3 = Fieldref           #21.#61        // com/wei/sample/clazz/TestTryCatch.feild:Ljava/lang/String;
       #4 = String             #62            // this is class volatile variables
       #5 = Fieldref           #21.#63        // com/wei/sample/clazz/TestTryCatch.volatileFeild:Ljava/lang/String;
       #6 = Fieldref           #64.#65        // java/lang/System.out:Ljava/io/PrintStream;
       #7 = String             #66            // from the class  block
       #8 = Methodref          #67.#68        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #9 = String             #69            // from static method
      #10 = String             #70            // from sync method
      #11 = String             #71            // from sync block
      #12 = Methodref          #21.#72        // com/wei/sample/clazz/TestTryCatch.staticMethod:()Ljava/lang/String;
      #13 = String             #73            // from try
      #14 = String             #74            // from finally
      #15 = Class              #75            // java/lang/Exception
      #16 = Methodref          #15.#76        // java/lang/Exception.printStackTrace:()V
      #17 = String             #77            // from catch
      #18 = String             #78            // this is class static variables
      #19 = Fieldref           #21.#79        // com/wei/sample/clazz/TestTryCatch.staticFeild:Ljava/lang/String;
      #20 = String             #80            // from the class static block
      #21 = Class              #81            // com/wei/sample/clazz/TestTryCatch
      #22 = Class              #82            // java/lang/Object
      #23 = Utf8               feild
      #24 = Utf8               Ljava/lang/String;
      #25 = Utf8               staticFeild
      #26 = Utf8               staticFinalFeild
      #27 = Utf8               ConstantValue
      #28 = String             #83            // this is class static final variables
      #29 = Utf8               volatileFeild
      #30 = Utf8               <init>
      #31 = Utf8               ()V
      #32 = Utf8               Code
      #33 = Utf8               LineNumberTable
      #34 = Utf8               LocalVariableTable
      #35 = Utf8               this
      #36 = Utf8               Lcom/wei/sample/clazz/TestTryCatch;
      #37 = Utf8               (Ljava/lang/String;)V
      #38 = Utf8               staticMethod
      #39 = Utf8               ()Ljava/lang/String;
      #40 = Utf8               a
      #41 = Utf8               I
      #42 = Utf8               b
      #43 = Utf8               syncMethod
      #44 = Utf8               syncBlock
      #45 = Utf8               StackMapTable
      #46 = Class              #81            // com/wei/sample/clazz/TestTryCatch
      #47 = Class              #82            // java/lang/Object
      #48 = Class              #84            // java/lang/Throwable
      #49 = Utf8               main
      #50 = Utf8               ([Ljava/lang/String;)Ljava/lang/String;
      #51 = Utf8               e
      #52 = Utf8               Ljava/lang/Exception;
      #53 = Utf8               args
      #54 = Utf8               [Ljava/lang/String;
      #55 = Class              #75            // java/lang/Exception
      #56 = Utf8               <clinit>
      #57 = Utf8               SourceFile
      #58 = Utf8               TestTryCatch.java
      #59 = NameAndType        #30:#31        // "<init>":()V
      #60 = Utf8               this is class variables
      #61 = NameAndType        #23:#24        // feild:Ljava/lang/String;
      #62 = Utf8               this is class volatile variables
      #63 = NameAndType        #29:#24        // volatileFeild:Ljava/lang/String;
      #64 = Class              #85            // java/lang/System
      #65 = NameAndType        #86:#87        // out:Ljava/io/PrintStream;
      #66 = Utf8               from the class  block
      #67 = Class              #88            // java/io/PrintStream
      #68 = NameAndType        #89:#37        // println:(Ljava/lang/String;)V
      #69 = Utf8               from static method
      #70 = Utf8               from sync method
      #71 = Utf8               from sync block
      #72 = NameAndType        #38:#39        // staticMethod:()Ljava/lang/String;
      #73 = Utf8               from try
      #74 = Utf8               from finally
      #75 = Utf8               java/lang/Exception
      #76 = NameAndType        #90:#31        // printStackTrace:()V
      #77 = Utf8               from catch
      #78 = Utf8               this is class static variables
      #79 = NameAndType        #25:#24        // staticFeild:Ljava/lang/String;
      #80 = Utf8               from the class static block
      #81 = Utf8               com/wei/sample/clazz/TestTryCatch
      #82 = Utf8               java/lang/Object
      #83 = Utf8               this is class static final variables
      #84 = Utf8               java/lang/Throwable
      #85 = Utf8               java/lang/System
      #86 = Utf8               out
      #87 = Utf8               Ljava/io/PrintStream;
      #88 = Utf8               java/io/PrintStream
      #89 = Utf8               println
      #90 = Utf8               printStackTrace
    {
      public static java.lang.String staticFeild;
        descriptor: Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_STATIC
    
      public static final java.lang.String staticFinalFeild;
        descriptor: Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        ConstantValue: String this is class static final variables
    
      public volatile java.lang.String volatileFeild;
        descriptor: Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_VOLATILE
    
      public com.wei.sample.clazz.TestTryCatch();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: ldc           #2                  // String this is class variables
             7: putfield      #3                  // Field feild:Ljava/lang/String;
            10: aload_0
            11: ldc           #4                  // String this is class volatile variables
            13: putfield      #5                  // Field volatileFeild:Ljava/lang/String;
            16: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
            19: ldc           #7                  // String from the class  block
            21: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            24: return
          LineNumberTable:
            line 24: 0
            line 11: 4
            line 14: 10
            line 21: 16
            line 25: 24
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      25     0  this   Lcom/wei/sample/clazz/TestTryCatch;
    
      public com.wei.sample.clazz.TestTryCatch(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: ldc           #2                  // String this is class variables
             7: putfield      #3                  // Field feild:Ljava/lang/String;
            10: aload_0
            11: ldc           #4                  // String this is class volatile variables
            13: putfield      #5                  // Field volatileFeild:Ljava/lang/String;
            16: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
            19: ldc           #7                  // String from the class  block
            21: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            24: aload_0
            25: aload_1
            26: putfield      #3                  // Field feild:Ljava/lang/String;
            29: return
          LineNumberTable:
            line 27: 0
            line 11: 4
            line 14: 10
            line 21: 16
            line 28: 24
            line 29: 29
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      30     0  this   Lcom/wei/sample/clazz/TestTryCatch;
                0      30     1 feild   Ljava/lang/String;
    
      public static java.lang.String staticMethod();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=2, args_size=0
             0: iconst_1
             1: istore_0
             2: bipush        10
             4: istore_1
             5: iload_1
             6: istore_0
             7: ldc           #9                  // String from static method
             9: areturn
          LineNumberTable:
            line 32: 0
            line 33: 2
            line 34: 5
            line 35: 7
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                2       8     0     a   I
                5       5     1     b   I
    
      public synchronized void syncMethod();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #10                 // String from sync method
             5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 39: 0
            line 40: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  this   Lcom/wei/sample/clazz/TestTryCatch;
    
      public void syncBlock();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter
             4: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
             7: ldc           #11                 // String from sync block
             9: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            12: aload_1
            13: monitorexit
            14: goto          22
            17: astore_2
            18: aload_1
            19: monitorexit
            20: aload_2
            21: athrow
            22: return
          Exception table:
             from    to  target type
                 4    14    17   any
                17    20    17   any
          LineNumberTable:
            line 43: 0
            line 44: 4
            line 45: 12
            line 46: 22
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      23     0  this   Lcom/wei/sample/clazz/TestTryCatch;
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 17
              locals = [ class com/wei/sample/clazz/TestTryCatch, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    
      public java.lang.String main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=5, args_size=2
             0: invokestatic  #12                 // Method staticMethod:()Ljava/lang/String;
             3: pop
             4: ldc           #13                 // String from try
             6: astore_2
             7: ldc           #14                 // String from finally
             9: areturn
            10: astore_2
            11: aload_2
            12: invokevirtual #16                 // Method java/lang/Exception.printStackTrace:()V
            15: ldc           #17                 // String from catch
            17: astore_3
            18: ldc           #14                 // String from finally
            20: areturn
            21: astore        4
            23: ldc           #14                 // String from finally
            25: areturn
          Exception table:
             from    to  target type
                 0     7    10   Class java/lang/Exception
                 0     7    21   any
                10    18    21   any
                21    23    21   any
          LineNumberTable:
            line 50: 0
            line 51: 4
            line 56: 7
            line 52: 10
            line 53: 11
            line 54: 15
            line 56: 18
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
               11      10     2     e   Ljava/lang/Exception;
                0      26     0  this   Lcom/wei/sample/clazz/TestTryCatch;
                0      26     1  args   [Ljava/lang/String;
          StackMapTable: number_of_entries = 2
            frame_type = 74 /* same_locals_1_stack_item */
              stack = [ class java/lang/Exception ]
            frame_type = 74 /* same_locals_1_stack_item */
              stack = [ class java/lang/Throwable ]
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: ldc           #18                 // String this is class static variables
             2: putstatic     #19                 // Field staticFeild:Ljava/lang/String;
             5: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: ldc           #20                 // String from the class static block
            10: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            13: return
          LineNumberTable:
            line 12: 0
            line 17: 5
            line 18: 13
    }
    SourceFile: "TestTryCatch.java"
    Process finished with exit code 0
    

    类信息和常量池

    Classfile /Users/shuxin.wei/Documents/icourtCode/sample/app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/wei/sample/clazz/TestTryCatch.class
      Last modified 2019-5-16; size 1901 bytes
      MD5 checksum 00ed76899d96d428782d4d128839aa9f
      Compiled from "TestTryCatch.java"
    public class com.wei.sample.clazz.TestTryCatch
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #22.#59        // java/lang/Object."<init>":()V
       #2 = String             #60            // this is class variables
    

    方法编译成字节码分析,以main方法为例

    方法描述

    public java.lang.String main(java.lang.String[]);
    

    descriptor:方法参数和返回值描述

    descriptor: ([Ljava/lang/String;)Ljava/lang/String;
    

    flags:方法属性描述

     flags: ACC_PUBLIC  ACC_STATIC  ACC_SYNCHONIZE
    

    code:方法栈描述

     Code:
          stack=1, locals=5, args_size=2
             0: invokestatic  #12                 // Method staticMethod:()Ljava/lang/String;
             3: pop
             4: ldc           #13                 // String from try
             6: astore_2
             7: ldc           #14                 // String from finally
             9: areturn
            10: astore_2
            11: aload_2
            12: invokevirtual #16                 // Method java/lang/Exception.printStackTrace:()V
            15: ldc           #17                 // String from catch
            17: astore_3
            18: ldc           #14                 // String from finally
            20: areturn
            21: astore        4
            23: ldc           #14                 // String from finally
            25: areturn
    

    stack:栈深度
    locals:本地变量个数
    args_size:

    指令 含义
    aload_0 这个操作码是aload格式操作码中的一个。它们用来把对象引用加载到操作码栈。 表示正在被访问的局部变量数组的位置,但只能是0、1、2、3 中的一个。还有一些其它类似的操作码用来载入非对象引用的数据,如iload, lload, float 和 dload。其中 i 表示 int,l 表示 long,f 表示 float,d 表示 double。局部变量数组位置大于 3 的局部变量可以用 iload, lload, float, dload 和 aload 载入。这些操作码都只需要一个操作数,即数组中的位置
    astore_2 将引用数据类型赋值后存储在变量表中的下标3位置
    bipush 变量压栈:当int取值-1~ 5采用iconst指令,取值-128~ 127采用bipush指令,取值-32768~ 32767采用sipush指令,取值-2147483648~ 2147483647采用 ldc 指令
    ldc 这个操作码用来将常量从运行时常量池压栈到操作数栈
    getstatic 这个操作码用来把一个静态变量从运行时常量池的静态变量列表中压栈到操作数栈
    invokespecial, invokevirtual 这些操作码属于一组函数调用的操作码,包括:invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual。在这个 class 文件中,invokespecial 和 invokevirutal 两个指令都用到了,两者的区别是,invokevirutal 指令调用一个对象的实例方法,invokespecial 指令调用实例初始化方法、私有方法、父类方法。
    return 这个操作码属于ireturn、lreturn、freturn、dreturn、areturn 和 return 操作码组。每个操作码返回一种类型的返回值,其中 i 表示 int,l 表示 long,f 表示 float,d 表示 double,a 表示 对象引用。没有前缀类型字母的 return 表示返回 void

    Exception table:异常跳转表

     Exception table:
             from    to  target type
                 0     7    10   Class java/lang/Exception
                 0     7    21   any
                10    18    21   any
                21    23    21   any
    

    从异常表中,可以看到有三种异常情况发生导致执行的路径不同:
    第一种:如果位于0到7字节之间的命令(即try块中的代码)抛出了Class java/lang/Exception类型的异常,则跳转到第10个字节开始执行catch中的代码;
    第二种:如果位于0到7字节之间的命令(即try块中的代码)抛出了任何类型的异常,则跳转到第21个字节开始执行finally里面的代码。
    第三种:如果位于10到18字节之间的命令(即catch块中的代码)跑出了任何类型的异常,则跳转到第21个字节开始执行finally里面的代码。
    第四种:如果位于21到23字节之间的命令(即catch块中的代码)跑出了任何类型的异常,则跳转到第21个字节开始执行finally里面的代码。

    LineNumberTable:行号表

    为调试器提供源码中的每一行对应的字节码信息。上面的例子中,Java 源码里的第 50 行与 TestTryCatch.java 函数字节码序号 0 相关,第 51 行与字节码序号 4 相关

     LineNumberTable:
            line 50: 0
            line 51: 4
            line 56: 7
    
    LineNumberTable.png

    LocalVariableTable:本地变量表

    存放局部变量,比如在try中出现异常后,会通过astore_2将Exception存到数组的第3个位置,也就是结合Exception table和LocalVariableTable来完成异常出现时的执行过程;

      LocalVariableTable:
            Start  Length  Slot  Name   Signature
               12      10     2     e   Ljava/lang/Exception;
                0      26     0  this   Lcom/wei/sample/clazz/TestTryCatch;
                0      26     1  args   [Ljava/lang/String;
    

    StackMapTable:栈图

    主要用于类加载时校验用的,类加载其中校验这步中会校验字节码是否非法,继承关系,地址偏移是否正确等,不用太关心;
    类加载可参考:https://www.jianshu.com/p/071874762b72
    StackMapTable具体可参考: https://blog.csdn.net/lijingyao8206/article/details/46715405

    StackMapTable: number_of_entries = 2
            frame_type = 74 /* same_locals_1_stack_item */
              stack = [ class java/lang/Exception ]
            frame_type = 74 /* same_locals_1_stack_item */
              stack = [ class java/lang/Throwable ]
    

    相关文章

      网友评论

        本文标题:Java 字节码阅读

        本文链接:https://www.haomeiwen.com/subject/uzxwaqtx.html