📓 Archive

JAVASTRACE

FGJ: Create:2024/03/07 Update: (2024-10-24)

  • Intro(JavaStrace) #

    一个java程序的运行全过程解析。

    右上java源代码,右下常量池,左下为Code方法块。

    {
        public _jvm.classPrase.Student(int, java.lang.String, java.lang.String);
            descriptor: (ILjava/lang/String;Ljava/lang/String;)V
            flags: ACC_PUBLIC
            Code:
            stack=3, locals=4, args_size=4
                0: aload_0
                1: iload_1
                2: aload_2
                3: invokespecial #1                  // Method _jvm/classPrase/Person."<init>":(ILjava/lang/String;)V
                6: aload_0
                7: aload_3
                8: putfield      #2                  // Field sid:Ljava/lang/String;
                11: return
            LineNumberTable:
                line 19: 0
                line 20: 6
                line 21: 11
    
        public void run();
            descriptor: ()V
            flags: ACC_PUBLIC
            Code:
            stack=2, locals=1, args_size=1
                0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                3: ldc           #4                  // String run()...
                5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                8: return
            LineNumberTable:
                line 23: 0
                line 24: 8
    
        public int study(int, int);
            descriptor: (II)I
            flags: ACC_PUBLIC
            Code:
            stack=3, locals=5, args_size=3
                0: bipush        10
                2: istore_3
                3: bipush        20
                5: istore        4
                7: iload_1
                8: iload_2
                9: iload_3
                10: imul
                11: iadd
                12: iload         4
                14: isub
                15: ireturn
            LineNumberTable:
                line 26: 0
                line 27: 3
                line 28: 7
    
        public static int getCnt();
            descriptor: ()I
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=1, locals=0, args_size=0
                0: getstatic     #6                  // Field cnt:I
                3: ireturn
            LineNumberTable:
                line 31: 0
    
        public static void main(java.lang.String[]);
            descriptor: ([Ljava/lang/String;)V
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=5, locals=2, args_size=1
                0: new           #7                  // class _jvm/classPrase/Student
                3: dup
                4: bipush        23
                6: ldc           #8                  // String dqrcsc
                8: ldc           #9                  // String 20150723
                10: invokespecial #10                 // Method "<init>":(ILjava/lang/String;Ljava/lang/String;)V
                13: astore_1
                14: aload_1
                15: iconst_5
                16: bipush        6
                18: invokevirtual #11                 // Method study:(II)I
                21: pop
                22: invokestatic  #12                 // Method getCnt:()I
                25: pop
                26: aload_1
                27: invokevirtual #13                 // Method run:()V
                30: return
            LineNumberTable:
                line 34: 0
                line 35: 14
                line 36: 22
                line 37: 26
                line 38: 30
    
        static {};
            descriptor: ()V
            flags: ACC_STATIC
            Code:
            stack=2, locals=0, args_size=0
                0: iconst_5
                1: putstatic     #6                  // Field cnt:I
                4: getstatic     #6                  // Field cnt:I
                7: iconst_1
                8: iadd
                9: putstatic     #6                  // Field cnt:I
                12: return
            LineNumberTable:
                line 13: 0
                line 15: 4
                line 16: 12
    }
    
    package _jvm.classPrase;
    
    /**
     *
     * Copyright https://wtfu.site Inc. All Rights Reserved.
     *
     * @date 2024/3/8
     *                          @since  1.0
     *                          @author 12302
     *
     */
    public class Student extends Person implements IStudyable {
        private static int cnt=5;
        static{
            cnt++;
        }
        private String sid;
        public Student(int age, String name, String sid){
            super(age,name);
            this.sid = sid;
        }
        public void run(){
            System.out.println("run()...");
        }
        public int study(int a, int b){
            int c = 10;
            int d = 20;
            return a+b*c-d;
        }
        public static int getCnt(){
            return cnt;
        }
        public static void main(String[] args){
            Student s = new Student(23,"dqrcsc","20150723");
            s.study(5,6);
            Student.getCnt();
            s.run();
        }
    }
    class Person {
        private String name;
        private int age;
        public Person(int age, String name){
            this.age = age;
            this.name = name;
        }
        public void run(){}
    }
    interface IStudyable {
        public int study(int a, int b);
    }
    
    Constant pool:
        #1 = Methodref          #14.#35        // _jvm/classPrase/Person."<init>":(ILjava/lang/String;)V
        #2 = Fieldref           #7.#36         // _jvm/classPrase/Student.sid:Ljava/lang/String;
        #3 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
        #4 = String             #39            // run()...
        #5 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
        #6 = Fieldref           #7.#42         // _jvm/classPrase/Student.cnt:I
        #7 = Class              #43            // _jvm/classPrase/Student
        #8 = String             #44            // dqrcsc
        #9 = String             #45            // 20150723
        #10 = Methodref          #7.#46         // _jvm/classPrase/Student."<init>":(ILjava/lang/String;Ljava/lang/String;)V
        #11 = Methodref          #7.#47         // _jvm/classPrase/Student.study:(II)I
        #12 = Methodref          #7.#48         // _jvm/classPrase/Student.getCnt:()I
        #13 = Methodref          #7.#49         // _jvm/classPrase/Student.run:()V
        #14 = Class              #50            // _jvm/classPrase/Person
        #15 = Class              #51            // _jvm/classPrase/IStudyable
        #16 = Utf8               cnt
        #17 = Utf8               I
        #18 = Utf8               sid
        #19 = Utf8               Ljava/lang/String;
        #20 = Utf8               <init>
        #21 = Utf8               (ILjava/lang/String;Ljava/lang/String;)V
        #22 = Utf8               Code
        #23 = Utf8               LineNumberTable
        #24 = Utf8               run
        #25 = Utf8               ()V
        #26 = Utf8               study
        #27 = Utf8               (II)I
        #28 = Utf8               getCnt
        #29 = Utf8               ()I
        #30 = Utf8               main
        #31 = Utf8               ([Ljava/lang/String;)V
        #32 = Utf8               <clinit>
        #33 = Utf8               SourceFile
        #34 = Utf8               Student.java
        #35 = NameAndType        #20:#52        // "<init>":(ILjava/lang/String;)V
        #36 = NameAndType        #18:#19        // sid:Ljava/lang/String;
        #37 = Class              #53            // java/lang/System
        #38 = NameAndType        #54:#55        // out:Ljava/io/PrintStream;
        #39 = Utf8               run()...
        #40 = Class              #56            // java/io/PrintStream
        #41 = NameAndType        #57:#58        // println:(Ljava/lang/String;)V
        #42 = NameAndType        #16:#17        // cnt:I
        #43 = Utf8               _jvm/classPrase/Student
        #44 = Utf8               dqrcsc
        #45 = Utf8               20150723
        #46 = NameAndType        #20:#21        // "<init>":(ILjava/lang/String;Ljava/lang/String;)V
        #47 = NameAndType        #26:#27        // study:(II)I
        #48 = NameAndType        #28:#29        // getCnt:()I
        #49 = NameAndType        #24:#25        // run:()V
        #50 = Utf8               _jvm/classPrase/Person
        #51 = Utf8               _jvm/classPrase/IStudyable
        #52 = Utf8               (ILjava/lang/String;)V
        #53 = Utf8               java/lang/System
        #54 = Utf8               out
        #55 = Utf8               Ljava/io/PrintStream;
        #56 = Utf8               java/io/PrintStream
        #57 = Utf8               println
        #58 = Utf8               (Ljava/lang/String;)V
    
    • 虚拟机栈 #

      JVM中通过java栈,保存方法调用运行的相关信息,每当调用一个方法,会根据该方法的在字节码中的信息为该方法创建栈帧,不同的方法,其栈帧的大小有所不同。栈帧中的内存空间还可以分为3块,分别存放不同的数据:

      1). 局部变量表:存放该方法调用者所传入的参数,及在该方法的方法体中创建的局部变量。
      2). 操作数栈:用于存放操作数及计算的中间结果等。
      3). 其他栈帧信息:如返回地址、当前方法的引用等。

      只有当前正在运行的方法的栈帧位于栈顶,当前方法返回,则当前方法对应的栈帧出栈,当前方法的调用者的栈帧变为栈顶;当前方法的方法体中若是调用了其他方法,则为被调用的方法创建栈帧,并将其压入栈顶。
      注意:局部变量表及操作数栈的最大深度在编译期间就已经确定了,存储在该方法字节码的Code属性中。

      • LocalVariableTable #

        局部变量表:在编译期间可以加-g:{none,lines,vars,source}参数生成相关调试信息。Code块可选的属性。可以用来debug,调试器可以使用它来确定方法执行期间给定局部变量的值。

        public int study(int a, int b){
            int c = 10;
            int d = 20;
            return a+b*c-d;
        }
        

        对于下列LocalVariableTable属性的解释:
        Start:code块有值时候的始偏移量,
        Length:code块有值时候的长度,
        Slot:存放变量的槽位。

        官方说有值: Each entry in the local_variable_type_table array indicates a range of code array offsets within which a local variable has a value. It also indicates the index into the local variable array of the current frame at which that local variable can be found. 有些地方说" 可见",一个意思。

        比如:
        1). this,a,b变量在code块中一直(0->16)有值。因为调用栈传入slot中的。
        2). 而c变量直到在 2: istore_3 存入第三个slot中时才有值,也就是在(3->16)位置有值。
        3). 同理d变量在 5: istore 4 存入第四个slot中时才有值,也就是在(7->16)位置有值。

        // 字节码节选
        public int study(int, int);
            descriptor: (II)I
            flags: ACC_PUBLIC
            Code:
            stack=3, locals=5, args_size=3
                0: bipush        10
                2: istore_3
                3: bipush        20
                5: istore        4
                7: iload_1
                8: iload_2
                9: iload_3
                10: imul
                11: iadd
                12: iload         4
                14: isub
                15: ireturn
            LineNumberTable:
                line 26: 0
                line 27: 3
                line 28: 7
            LocalVariableTable:
                Start  Length  Slot  Name   Signature
                    0      16     0  this   L_jvm/classPrase/Student;
                    0      16     1     a   I
                    0      16     2     b   I
                    3      13     3     c   I
                    7       9     4     d   I
        
    • 运行过程 #

      into-main() #

      main方法的运行过程

      stack 指定当前方法即main()方法对应栈帧中的操作数栈的最大深度,当前值为5
      locals 指定main()方法中局部变量表的大小,当前为2,及有两个slot用于存放方法的参数及局部变量。
      code length 指定main()方法中代码的长度,当前为31。

      public static void main(String[] args){
          Student s = new Student(23,"dqrcsc","20150723");
          s.study(5,6);
          Student.getCnt();
          s.run();
      }
      

      public static void main(java.lang.String[]);
          descriptor: ([Ljava/lang/String;)V
          flags: ACC_PUBLIC, ACC_STATIC
          Code:
          stack=5, locals=2, args_size=1
              0: new           #7                  // class _jvm/classPrase/Student
              3: dup
              4: bipush        23
              6: ldc           #8                  // String dqrcsc
              8: ldc           #9                  // String 20150723
              10: invokespecial #10                 // Method "<init>":(ILjava/lang/String;Ljava/lang/String;)V
              13: astore_1
              14: aload_1
              15: iconst_5
              16: bipush        6
              18: invokevirtual #11                 // Method study:(II)I
              21: pop
              22: invokestatic  #12                 // Method getCnt:()I
              25: pop
              26: aload_1
              27: invokevirtual #13                 // Method run:()V
              30: return
          LineNumberTable:
              line 34: 0
              line 35: 14
              line 36: 22
              line 37: 26
              line 38: 30
      

      new #7指令:在java堆中创建一个Student对象,并将其引用值放入栈顶。

      dup:复制栈顶的值,然后将复制的结果入栈。
      bipush 23:将单字节常量值23入栈。
      ldc #8:将#8这个常量池中的常量即”dqrcsc”取出,并入栈。
      ldc #9:将#9这个常量池中的常量即”20150723”取出,并入栈。

      into-Student.construct() #

      invokespecial #10:调用#10这个常量所代表的方法,即Student.<init>()这个方法

      <init>()方法,是编译器将调用父类的<init>()的语句、构造代码块、实例字段赋值语句,以及自己编写的构造方法中的语句整合在一起生成的一个方法。保证调用父类的<init>()方法在最开头,自己编写的构造方法语句在最后,而构造代码块及实例字段赋值语句按出现的顺序按序整合到<init>()方法中。

      注意到Student.<init>()方法的最大操作数栈深度为3,局部变量表大小为4。 从dup到ldc #9这四条指令向栈中添加了4个数据,虽然定义中只显式地定义了传入3个参数,而实际上会隐含传入一个当前对象的引用作为第一个参数,所以四个参数依次为this,age,name,sid。参数传递后调用了Student.<init>()方法,会创建该方法的栈帧,并入栈。栈帧中的局部变量表的第0到4个slot分别保存着入栈的那四个参数值。

      public Student(int age, String name, String sid){
          super(age,name);
          this.sid = sid;
      }
      

      public Person(int age, String name){
          this.age = age;
          this.name = name;
      }
      
      public _jvm.classPrase.Student(int, java.lang.String, java.lang.String);
          descriptor: (ILjava/lang/String;Ljava/lang/String;)V
          flags: ACC_PUBLIC
          Code:
          stack=3, locals=4, args_size=4
              0: aload_0
              1: iload_1
              2: aload_2
              3: invokespecial #1                  // Method _jvm/classPrase/Person."<init>":(ILjava/lang/String;)V
              6: aload_0
              7: aload_3
              8: putfield      #2                  // Field sid:Ljava/lang/String;
              11: return
          LineNumberTable:
              line 19: 0
              line 20: 6
              line 21: 11
      

      aload_0:将局部变量表slot0处的引用值入栈
      iload_1:将局部变量表slot1处的int值入栈
      aload_2:将局部变量表slot2处的引用值入栈

      invokespecial #1:调用Person.<init>()方法,同调用Student.<init>过程类似,创建栈帧,将三个参数的值存放到局部变量表等,从Person.()返回之后,用于传参的栈顶的3个值被回收了。
      aload_0:将slot0处的引用值入栈。
      aload_3:将slot3处的引用值入栈。

      putfield #2:将当前栈顶的值”20150723”赋值给0x2222所引用对象的sid字段,然后栈中的两个值出栈。
      return:返回调用方,即main()方法,当前方法栈帧出栈。

      重新回到main()方法中,继续执行下面的字节码指令:
      astore_1:将当前栈顶引用类型的值赋值给slot1处的局部变量,然后出栈。
      aload_1:slot1处的引用类型的值入栈。
      iconst_5:将常数5入栈,int型常数只有0-5有对应的iconst_x指令。
      bipush 6:将常数6入栈。

      into-s.study() #

      invokevirtual #11:调用虚方法study(),这个方法是重写的接口中的方法,需要动态分派,所以使用了invokevirtual指令。

      最大栈深度3,局部变量表5。

      public int study(int a, int b){
          int c = 10;
          int d = 20;
          return a+b*c-d;
      }
      

      public int study(int, int);
          descriptor: (II)I
          flags: ACC_PUBLIC
          Code:
          stack=3, locals=5, args_size=3
              0: bipush        10
              2: istore_3
              3: bipush        20
              5: istore        4
              7: iload_1
              8: iload_2
              9: iload_3
              10: imul
              11: iadd
              12: iload         4
              14: isub
              15: ireturn
          LineNumberTable:
              line 26: 0
              line 27: 3
              line 28: 7
      

      bipush 10:将10入栈
      istore_3:将栈顶的10赋值给slot3处的int局部变量,即c,出栈。
      bipush 20:将20入栈
      istore 4:将栈顶的20付给slot4处的int局部变量,即d,出栈。
      上面4条指令,完成对c和d的赋值工作。
      iload_1、iload_2、iload_3这三条指令将slot1、slot2、slot3这三个局部变量入栈:

      imul:将栈顶的两个值出栈,相乘的结果入栈:

      iadd:将当前栈顶的两个值出栈,相加的结果入栈
      iload 4:将slot4处的int型的局部变量入

      isub:将栈顶两个值出栈,相减结果入栈:
      ireturn:将当前栈顶的值返回到调用方。

      into-Student.getCnt()-and-s.run() #

      重新回到main()方法中:
      pop指令,将study()方法的返回值出栈
      invokestatic #12 调用静态方法getCnt()不需要传任何参数
      pop:getCnt()方法有返回值,将其出栈
      aload_1:将slot1处的引用值入栈
      invokevirtual #13:调用0x2222对象的run()方法,重写自父类的方法,需要动态分派,所以使用invokevirtual指令
      return:main()返回,程序运行结束。

  • Reference #


comments powered by Disqus