• 【JVM学习笔记】字节码文件结构实例


    上一篇笔记的内容大部分没有实际动手操作,因此决定完成这个完整的练习并记录下来。

    另注,idea环境下有jclasslib插件用于更好的查看类似于javap结果的内容。

    源代码如下:

    package com.learn.jvm;
    
    /**
     * @Description
     * @date 2019/09/05 16:31
     */
    public class Test {
        String str = "Welcome";
        private int x = 5;
        public static Integer num = 10;
    
        public static void main(String[] args) {
            Test test = new Test();
            test.setX(8);
            num = 20;
        }
    
        private synchronized void setX(int x) {
            this.x = x;
        }
    
        private void test1(String str) {
            // 注意,这里会对str对象本身加锁,如果str是"abc",则是对"abc"这个字符串对象加锁,如果str是"def",则是对"def"这个对象加锁
            // 所以实际上这种写法无法实现正常的加锁功能,只是了学习字节码而举的例子
            synchronized (str) {
                System.out.println("hello world");
            }
        }
    
        private synchronized  static void test2(){}
    }

    编译后在Windows平台下进入wsl,使用命令vim Test.class,然后输入:%!xxd就可以看到16进制代码。

    也可以使用Linux下的xxd命令,将二进制信息转换为16进制数据,使用方式为 xxd Test.class Test.txt,生成的HelloWorld.txt与通过:%!xxd是一样的(实际操作时返现在结尾处略有不同,生成txt文件少了1个字节)

    00000000: cafe babe 0000 0033 0046 0a00 0d00 2d08  .......3.F....-.
    00000010: 002e 0900 0500 2f09 0005 0030 0700 310a  ....../....0..1.
    00000020: 0005 002d 0a00 0500 320a 0033 0034 0900  ...-....2..3.4..
    00000030: 0500 3509 0036 0037 0800 380a 0039 003a  ..5..6.7..8..9.:
    00000040: 0700 3b01 0003 7374 7201 0012 4c6a 6176  ..;...str...Ljav
    00000050: 612f 6c61 6e67 2f53 7472 696e 673b 0100  a/lang/String;..
    00000060: 0178 0100 0149 0100 036e 756d 0100 134c  .x...I...num...L
    00000070: 6a61 7661 2f6c 616e 672f 496e 7465 6765  java/lang/Intege
    00000080: 723b 0100 063c 696e 6974 3e01 0003 2829  r;...<init>...()
    00000090: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN
    000000a0: 756d 6265 7254 6162 6c65 0100 124c 6f63  umberTable...Loc
    000000b0: 616c 5661 7269 6162 6c65 5461 626c 6501  alVariableTable.
    000000c0: 0004 7468 6973 0100 144c 636f 6d2f 6c65  ..this...Lcom/le
    000000d0: 6172 6e2f 6a76 6d2f 5465 7374 3b01 0004  arn/jvm/Test;...
    000000e0: 6d61 696e 0100 1628 5b4c 6a61 7661 2f6c  main...([Ljava/l
    000000f0: 616e 672f 5374 7269 6e67 3b29 5601 0004  ang/String;)V...
    00000100: 6172 6773 0100 135b 4c6a 6176 612f 6c61  args...[Ljava/la
    00000110: 6e67 2f53 7472 696e 673b 0100 0474 6573  ng/String;...tes
    00000120: 7401 0004 7365 7458 0100 0428 4929 5601  t...setX...(I)V.
    00000130: 0005 7465 7374 3101 0015 284c 6a61 7661  ..test1...(Ljava
    00000140: 2f6c 616e 672f 5374 7269 6e67 3b29 5601  /lang/String;)V.
    00000150: 000d 5374 6163 6b4d 6170 5461 626c 6507  ..StackMapTable.
    00000160: 0031 0700 3c07 003b 0700 3d01 0005 7465  .1..<..;..=...te
    00000170: 7374 3201 0008 3c63 6c69 6e69 743e 0100  st2...<clinit>..
    00000180: 0a53 6f75 7263 6546 696c 6501 0009 5465  .SourceFile...Te
    00000190: 7374 2e6a 6176 610c 0014 0015 0100 0757  st.java........W
    000001a0: 656c 636f 6d65 0c00 0e00 0f0c 0010 0011  elcome..........
    000001b0: 0100 1263 6f6d 2f6c 6561 726e 2f6a 766d  ...com/learn/jvm
    000001c0: 2f54 6573 740c 0020 0021 0700 3e0c 003f  /Test.. .!..>..?
    000001d0: 0040 0c00 1200 1307 0041 0c00 4200 4301  .@.......A..B.C.
    000001e0: 000b 6865 6c6c 6f20 776f 726c 6407 0044  ..hello world..D
    000001f0: 0c00 4500 2301 0010 6a61 7661 2f6c 616e  ..E.#...java/lan
    00000200: 672f 4f62 6a65 6374 0100 106a 6176 612f  g/Object...java/
    00000210: 6c61 6e67 2f53 7472 696e 6701 0013 6a61  lang/String...ja
    00000220: 7661 2f6c 616e 672f 5468 726f 7761 626c  va/lang/Throwabl
    00000230: 6501 0011 6a61 7661 2f6c 616e 672f 496e  e...java/lang/In
    00000240: 7465 6765 7201 0007 7661 6c75 654f 6601  teger...valueOf.
    00000250: 0016 2849 294c 6a61 7661 2f6c 616e 672f  ..(I)Ljava/lang/
    00000260: 496e 7465 6765 723b 0100 106a 6176 612f  Integer;...java/
    00000270: 6c61 6e67 2f53 7973 7465 6d01 0003 6f75  lang/System...ou
    00000280: 7401 0015 4c6a 6176 612f 696f 2f50 7269  t...Ljava/io/Pri
    00000290: 6e74 5374 7265 616d 3b01 0013 6a61 7661  ntStream;...java
    000002a0: 2f69 6f2f 5072 696e 7453 7472 6561 6d01  /io/PrintStream.
    000002b0: 0007 7072 696e 746c 6e00 2100 0500 0d00  ..println.!.....
    000002c0: 0000 0300 0000 0e00 0f00 0000 0200 1000  ................
    000002d0: 1100 0000 0900 1200 1300 0000 0600 0100  ................
    000002e0: 1400 1500 0100 1600 0000 4200 0200 0100  ..........B.....
    000002f0: 0000 102a b700 012a 1202 b500 032a 08b5  ...*...*.....*..
    00000300: 0004 b100 0000 0200 1700 0000 0e00 0300  ................
    00000310: 0000 0800 0400 0900 0a00 0a00 1800 0000  ................
    00000320: 0c00 0100 0000 1000 1900 1a00 0000 0900  ................
    00000330: 1b00 1c00 0100 1600 0000 5700 0200 0200  ..........W.....
    00000340: 0000 17bb 0005 59b7 0006 4c2b 1008 b700  ......Y...L+....
    00000350: 0710 14b8 0008 b300 09b1 0000 0002 0017  ................
    00000360: 0000 0012 0004 0000 000e 0008 000f 000e  ................
    00000370: 0010 0016 0011 0018 0000 0016 0002 0000  ................
    00000380: 0017 001d 001e 0000 0008 000f 001f 001a  ................
    00000390: 0001 0022 0020 0021 0001 0016 0000 003e  ...". .!.......>
    000003a0: 0002 0002 0000 0006 2a1b b500 04b1 0000  ........*.......
    000003b0: 0002 0017 0000 000a 0002 0000 0014 0005  ................
    000003c0: 0015 0018 0000 0016 0002 0000 0006 0019  ................
    000003d0: 001a 0000 0000 0006 0010 0011 0001 0002  ................
    000003e0: 0022 0023 0001 0016 0000 0085 0002 0004  .".#............
    000003f0: 0000 0017 2b59 4dc2 b200 0a12 0bb6 000c  ....+YM.........
    00000400: 2cc3 a700 084e 2cc3 2dbf b100 0200 0400  ,....N,.-.......
    00000410: 0e00 1100 0000 1100 1400 1100 0000 0300  ................
    00000420: 1700 0000 1200 0400 0000 1a00 0400 1b00  ................
    00000430: 0c00 1c00 1600 1d00 1800 0000 1600 0200  ................
    00000440: 0000 1700 1900 1a00 0000 0000 1700 0e00  ................
    00000450: 0f00 0100 2400 0000 1800 02ff 0011 0003  ....$...........
    00000460: 0700 2507 0026 0700 2700 0107 0028 fa00  ..%..&..'....(..
    00000470: 0400 2a00 2900 1500 0100 1600 0000 1900  ..*.)...........
    00000480: 0000 0000 0000 01b1 0000 0001 0017 0000  ................
    00000490: 0006 0001 0000 001f 0008 002a 0015 0001  ...........*....
    000004a0: 0016 0000 0021 0001 0000 0000 0009 100a  .....!..........
    000004b0: b800 08b3 0009 b100 0000 0100 1700 0000  ................
    000004c0: 0600 0100 0000 0b00 0100 2b00 0000 0200  ..........+.....
    000004d0: 2c0a                                     ,.

    javap -verbose -p 的输出结果如下(不待参数p则不会显示私有成员)

    D:workspace-learncommon-learnlearn-classloader	argetclassescomlearnjvm>javap -verbose -p Test
    警告: 二进制文件Test包含com.learn.jvm.Test
    Classfile /D:/workspace-learn/common-learn/learn-classloader/target/classes/com/learn/jvm/Test.class
      Last modified 2019-9-5; size 1233 bytes
      MD5 checksum d7a9950df521ddf0ee5c7b76c1a25796
      Compiled from "Test.java"
    public class com.learn.jvm.Test
      SourceFile: "Test.java"
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #13.#45        //  java/lang/Object."<init>":()V
       #2 = String             #46            //  Welcome
       #3 = Fieldref           #5.#47         //  com/learn/jvm/Test.str:Ljava/lang/String;
       #4 = Fieldref           #5.#48         //  com/learn/jvm/Test.x:I
       #5 = Class              #49            //  com/learn/jvm/Test
       #6 = Methodref          #5.#45         //  com/learn/jvm/Test."<init>":()V
       #7 = Methodref          #5.#50         //  com/learn/jvm/Test.setX:(I)V
       #8 = Methodref          #51.#52        //  java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       #9 = Fieldref           #5.#53         //  com/learn/jvm/Test.num:Ljava/lang/Integer;
      #10 = Fieldref           #54.#55        //  java/lang/System.out:Ljava/io/PrintStream;
      #11 = String             #56            //  hello world
      #12 = Methodref          #57.#58        //  java/io/PrintStream.println:(Ljava/lang/String;)V
      #13 = Class              #59            //  java/lang/Object
      #14 = Utf8               str
      #15 = Utf8               Ljava/lang/String;
      #16 = Utf8               x
      #17 = Utf8               I
      #18 = Utf8               num
      #19 = Utf8               Ljava/lang/Integer;
      #20 = Utf8               <init>
      #21 = Utf8               ()V
      #22 = Utf8               Code
      #23 = Utf8               LineNumberTable
      #24 = Utf8               LocalVariableTable
      #25 = Utf8               this
      #26 = Utf8               Lcom/learn/jvm/Test;
      #27 = Utf8               main
      #28 = Utf8               ([Ljava/lang/String;)V
      #29 = Utf8               args
      #30 = Utf8               [Ljava/lang/String;
      #31 = Utf8               test
      #32 = Utf8               setX
      #33 = Utf8               (I)V
      #34 = Utf8               test1
      #35 = Utf8               (Ljava/lang/String;)V
      #36 = Utf8               StackMapTable
      #37 = Class              #49            //  com/learn/jvm/Test
      #38 = Class              #60            //  java/lang/String
      #39 = Class              #59            //  java/lang/Object
      #40 = Class              #61            //  java/lang/Throwable
      #41 = Utf8               test2
      #42 = Utf8               <clinit>
      #43 = Utf8               SourceFile
      #44 = Utf8               Test.java
      #45 = NameAndType        #20:#21        //  "<init>":()V
      #46 = Utf8               Welcome
      #47 = NameAndType        #14:#15        //  str:Ljava/lang/String;
      #48 = NameAndType        #16:#17        //  x:I
      #49 = Utf8               com/learn/jvm/Test
      #50 = NameAndType        #32:#33        //  setX:(I)V
      #51 = Class              #62            //  java/lang/Integer
      #52 = NameAndType        #63:#64        //  valueOf:(I)Ljava/lang/Integer;
      #53 = NameAndType        #18:#19        //  num:Ljava/lang/Integer;
      #54 = Class              #65            //  java/lang/System
      #55 = NameAndType        #66:#67        //  out:Ljava/io/PrintStream;
      #56 = Utf8               hello world
      #57 = Class              #68            //  java/io/PrintStream
      #58 = NameAndType        #69:#35        //  println:(Ljava/lang/String;)V
      #59 = Utf8               java/lang/Object
      #60 = Utf8               java/lang/String
      #61 = Utf8               java/lang/Throwable
      #62 = Utf8               java/lang/Integer
      #63 = Utf8               valueOf
      #64 = Utf8               (I)Ljava/lang/Integer;
      #65 = Utf8               java/lang/System
      #66 = Utf8               out
      #67 = Utf8               Ljava/io/PrintStream;
      #68 = Utf8               java/io/PrintStream
      #69 = Utf8               println
    {
      java.lang.String str;
        flags:
    
      private int x;
        flags: ACC_PRIVATE
    
      public static java.lang.Integer num;
        flags: ACC_PUBLIC, ACC_STATIC
    
      public com.learn.jvm.Test();
        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 Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: return
          LineNumberTable:
            line 8: 0
            line 9: 4
            line 10: 10
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0      16     0  this   Lcom/learn/jvm/Test;
    
      public static void main(java.lang.String[]);
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #5                  // class com/learn/jvm/Test
             3: dup
             4: invokespecial #6                  // Method "<init>":()V
             7: astore_1
             8: aload_1
             9: bipush        8
            11: invokespecial #7                  // Method setX:(I)V
            14: bipush        20
            16: invokestatic  #8                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            19: putstatic     #9                  // Field num:Ljava/lang/Integer;
            22: return
          LineNumberTable:
            line 14: 0
            line 15: 8
            line 16: 14
            line 17: 22
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0      23     0  args   [Ljava/lang/String;
                   8      15     1  test   Lcom/learn/jvm/Test;
    
      private synchronized void setX(int);
        flags: ACC_PRIVATE, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: iload_1
             2: putfield      #4                  // Field x:I
             5: return
          LineNumberTable:
            line 20: 0
            line 21: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0       6     0  this   Lcom/learn/jvm/Test;
                   0       6     1     x   I
    
      private void test1(java.lang.String);
        flags: ACC_PRIVATE
        Code:
          stack=2, locals=4, args_size=2
             0: aload_1
             1: dup
             2: astore_2
             3: monitorenter
             4: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
             7: ldc           #11                 // String hello world
             9: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            12: aload_2
            13: monitorexit
            14: goto          22
            17: astore_3
            18: aload_2
            19: monitorexit
            20: aload_3
            21: athrow
            22: return
          Exception table:
             from    to  target type
                 4    14    17   any
                17    20    17   any
          LineNumberTable:
            line 26: 0
            line 27: 4
            line 28: 12
            line 29: 22
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0      23     0  this   Lcom/learn/jvm/Test;
                   0      23     1   str   Ljava/lang/String;
          StackMapTable: number_of_entries = 2
               frame_type = 255 /* full_frame */
              offset_delta = 17
              locals = [ class com/learn/jvm/Test, class java/lang/String, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
               frame_type = 250 /* chop */
              offset_delta = 4
    
    
      private static synchronized void test2();
        flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
        Code:
          stack=0, locals=0, args_size=0
             0: return
          LineNumberTable:
            line 31: 0
    
      static {};
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: bipush        10
             2: invokestatic  #8                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             5: putstatic     #9                  // Field num:Ljava/lang/Integer;
             8: return
          LineNumberTable:
            line 11: 0
    }

     同时贴出Java字节码整体结构图便于对比查看

    以下开始逐个字节进行分析(仅分析一部分重要字节和代表性的字节,因为太多了)

    第1~4个字节:ca fe ba be 为魔数

    第5~6个字节:00 00 代表 minor version ,值为0

    第7~8个字节:00 33 代表 major version ,值为51

    第9~10个字节:00 46 ,换算成十进制为 4*16+6 = 70 ,代表常量池中共有69个常量(除去一个为null保留的常量),我们通过查看上面 avap -verbose -p打印出来的信息可以验证,确实是这样的

    接下来是具体的常量分析,所以将常量池中的数据类型结构图,和相关知识要点贴出来

    要点:

    1. 使用java -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息
    2. 魔数:所有.class字节码文件的前四个字节都是魔数,,魔数值为固定值:0xCAFEBABE
    3. 魔数之后的4个字节代码版本号,前2个字节代表次版本号(minor version),后2个字节代表主版本号(major version),以上图为例,次版本号为0,主版本号为52,所以,该文件的版本号是1.8.0
    4. 常量池(constant pool):紧接着主版本号之后的就是常量池入口。一个Java类定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号应用。字面量如文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等
    5. 常量池的总体结构:Java类所对应的常量池主要由“常量池数量”与“常量池数组”这两部分共同构成。常量池数量紧跟在主版本号后面(也就是第9个字节开始),占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的原素的类型、结构都是不同的,长度当然也就不同,但是,每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。以上图为例,第9到第10个字节连起来时 0018,即十进制数字的24,意即后面有24个常量。值得注意的是,常量池数组中元素的个数 = 常量池数 - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池”的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量池数组(也叫常量表)中,这个常量就对应null值,所以常量池的索引从1而非0开始。以上图为例显示有24个常量,但是根据 java -verbose的输出结果,显示只有23个常量。
    6. 在JVM规范种,每个变量/字段都有描述信息,描述信息的主要作用是描述字段的数据类型、方法的参数列表(包括数量、类型与顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,而对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只使用一个大写字母,如下所示:B - byte ,C - char,D - double,F - float, I - int,J - long,S - short,Z - boolean,V - void,L - 对象类型,如Ljava/lang/String;
    7. 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;
    8. 用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之类,如方法String getRealNameByIdAndName(int id, String name),表示为(I,Ljava/lang/String;)Ljava/lang/String;

    (以下为第1个常量)

    第11个字节:0a,换算成十进制为 10,代表该常量是上图中的 CONSTANT_Methodref_info 类型

    第12~13个字节(表示这个方法时由哪个类所定义的):00 0d,换算成十进制为13,根据本文开头javap -verbose -p 的结果,最终可以看到这个索引项指向一个Class类型,值为 "java/lang/Object"

    第14~15个字节(对方法本身的描述):00 2d,换算成十进制为45,从常量池中找到结果为 "<init>":()V ,即该方法名称为<init> (注:也就是构造方法),参数列表为空,返回值类型为void

    (以下为第2个常量)

    第16个字节:08,代表上图中的 CONSTANT_String_info 类型

    第17~第18个字节:00 2e,十进制为46,引用的是字符串常量"Welcome"

    (以下为第3个常量)

    第19个字节:09,代表上图中的 CONSTANT_Fieldref_info 类型

    第20~21个字节:00 05,引用值为 "com/learn/jvm/Test",表示该字段是在Test类中声明的

    第22~23个字节:00 2f,十进制为47,值为 str:Ljava/lang/String; 表示该字段名为str,类型是一个String对象

    (以下为第4个常量)

    第24个字节:09,和第三个常量类似,此处省略

    (以下为第5个常量)

    第29个字节:07,代表上图中的 CONSTANT_Class_info 类型

    第30~31个字节:00 31,十进制为49,引用值为 "com/learn/jvm/Test"

    (以下为第6个常量)

    第32个字节:0a,与第1个常量类似,此处省略

    (以下为第7个常量)

    第37个字节:0a,省略

    (以下为第8个变量)

    第42个字节:0a,是定义在java/lang/Integer类中的方法,方法名为 valueOf,参数列表为int,返回一个Ljava/lang/Integer对象。之所以存在这个方法,是因为我们在程序中将一个int值赋给了Integer,导致自动装箱。

    (以下为第9个变量)

    第47个字节:09,省略

    ······

     常量池到此结束

    Access_Flag访问标志部分

    访问标志信息包括该Class文件是类还是接口,是否被定义为public,是否是abstract,如果是类,是否被声明为final

    上图缺少了0x0002代表 ACC_PRIVATE

    (以下为Access Flags)

    第??~??个字节:00 21 ,对应于上图就是 ACC_PUBLIC | ACC_SUPER (即这两者的并集)

    The Class Name

    第??~??个字节:00 05,对应常量池中索引为5的"com/learn/jvm/Test"

    Super Class Name

    第??~??个字节:00 0d,对应常量池中的索引为13的"java/lang/Object"

    Interfaces

    第??~??个字节:00 00,代表接口数量为0,我们的类没有实现任何接口

    Fields (2+N个字节)

    接下就到了字段表集合了,相关知识如下:

    字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。

    上面两张图是一个意思,都是介绍了字段表集合的结构

    第??~??个字节:00 03,代表有3个成员变量

    (以下为第1个成员变量)

    access_flags 为 00 00 ,代表默认访问级别(即 "default")

    name_index 为 00 0e,即常量池中索引值为14指向的常量  "str"

    descriptor_index 为 00 0f,在常量池中找到为 "Ljava/lang/String;"

    attributes_count 为 00 00,属性数量为0,表示没有属性 (备注:属性是什么??)

    由于属性数量为0,attribute_info也就没有了

    (以下为第2个成员变量)

    0002 ,代表访问修饰符为 private

    0010,在常量池中找到值为 "x" ,表示变量名为 x

    0011,在常量池中找到值为 "I",表明这个成员变量的类型为 int

    0000,表示属性数量为0

    (以下为第三个成员变量)

    0009,代表访问修饰符为 public static (上图中找不到0009的解释,但是在jclasslib插件中可以看到是public static)

    0012,在常量池中找到为num,表示变量名为num

    0013,在常量池找到为  Ljava/lang/Integer;  表示变量类型

    0000,属性数量为0

    Method  部分 ,前两个字节表示方法数量

    0006,表示有6个方法,从下图jclasslib的结果中也可以看出确实是6个,其中最后一个方法是对静态信息的初始化,包括静态的成员变量和静态块

    下面开始逐个分析这些方法的十六进制代码,先贴出方法表相关的知识:

     其中 attribute_info,这个属性跟方法里面的成员变量不是一回事,这个属性是归属于这个方法本身的。javac在编译好一个字节码文件之后,会给相应的方法额外增加一些attribute信息,这些attribute信息就描述了这个方法,比如方法的执行字节码是什么,方法的行号表是什么,局部变量表是什么,attribute_count就表明这些属性一共有多少个。attribute_info结构如下

    注意,上图中的 u1 info[attribute_length]其实放在这里这种写法是很容易造成混淆的,其实它的意思仅仅是表示有attribute_length个info,每个info是一个字节。我在这里晕了半天,搞懂了,所以记录一下。

     

    注意,其中 u1 code[code_length]表示的是一共有code_length个code,每个code是1个字节。据里来说,u4 code_length的值是10,那么说明接下来10个字节是虚拟机真正执行的质量,即助记符,u1 code[10]就表示这个意思。这张图这个地方是很容易造成误解的,所以我理解之后做个这个备注进行说明。

     

     

    (第一个方法)

    0001:代表访问修饰符为 ACC_PUBLIC

    0014:方法名字索引,十进制为20,在常量池中找到为<init>

    0015:方法描述符,十进制为21,在常量池中找到为()V,表示参数个数为0,返回类型为void

    0001:表示属性个数为1个

    (以下为第1个方法的attribute_info)

     0016:属性名,十进制为22,在常量池中找到值为"Code",表明从这里开始,是一个Code类型的属性,Code的结构参考上面的几张图

    00000042:属性长度,表示一共66个字节,即接下来66个字节就是这个方法真正所执行的代码对应的指令

    (以下为第1个方法的attribute_info的Code属性(即Code_attribute除去名字和长度字段的内容)的内容)

    0002:(max_stack) ,最大操作数栈深度为2

    0001:(max_locals),

    00000010:(code_length),接下来16个字节就是该方法对应的真正的虚拟机将要执行的指令,这些十六进制代码对应了各自的助记符

    -----------------------------------------------------------------------------------------------

    手工翻译的工作暂时到此为止吧,有点单调乏味,下面用jclasslib插件来看其他一些需要学习的点

    -----------------------------------------------------------------------------------------------

    1. 构造函数

    从上图中可以看出,实际上我们对str和x这两个变量的赋值是在自动生成的构造方法里完成的。这就有一个疑问,上面这种情况是在我们没有自定义构造函数的情况下发生的,如果我们定义了构造函数,情况又将如何?

      可以看出,编译器将x和str的赋值合并到了我们自定义的构造方法里面

    如果我们有两个构造方法,又是什么情况呢?

     

     试了一下,结果发现如上图,生成的字节码中,两个构造函数里均有对x和str赋值的相关代码

    2.关于锁

    先逐个解释这些助记符(助记符文档 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.dup)

    aload_1:将局部变量表中的1号变量的值压入栈顶,即将str变量压入栈顶,注意,0号参数是this,所以str只能是1号变量

    dup:复制操作数堆栈上的顶部值,并将复制 的值压入操作数堆栈。

    先解释到这里吧,感觉还需要学习。

  • 相关阅读:
    二 .数据库(Data)操作
    一. 数据库(Data)基础
    五种IO/模型
    并发编程 (协程)
    七.并发编程 (线程池,返回值,回调函数)
    六.并发编程 (线程对列)
    五.并发编程 (线程事件)
    四.并发编程 (线程信号量)
    三.并发编程 (线程锁)
    二.并发编程 (程序中线程操作)
  • 原文地址:https://www.cnblogs.com/heben/p/11468285.html
Copyright © 2020-2023  润新知