上一篇笔记的内容大部分没有实际动手操作,因此决定完成这个完整的练习并记录下来。
另注,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打印出来的信息可以验证,确实是这样的
接下来是具体的常量分析,所以将常量池中的数据类型结构图,和相关知识要点贴出来
要点:
- 使用java -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息
- 魔数:所有.class字节码文件的前四个字节都是魔数,,魔数值为固定值:0xCAFEBABE
- 魔数之后的4个字节代码版本号,前2个字节代表次版本号(minor version),后2个字节代表主版本号(major version),以上图为例,次版本号为0,主版本号为52,所以,该文件的版本号是1.8.0
- 常量池(constant pool):紧接着主版本号之后的就是常量池入口。一个Java类定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号应用。字面量如文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等
- 常量池的总体结构:Java类所对应的常量池主要由“常量池数量”与“常量池数组”这两部分共同构成。常量池数量紧跟在主版本号后面(也就是第9个字节开始),占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的原素的类型、结构都是不同的,长度当然也就不同,但是,每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。以上图为例,第9到第10个字节连起来时 0018,即十进制数字的24,意即后面有24个常量。值得注意的是,常量池数组中元素的个数 = 常量池数 - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池”的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量池数组(也叫常量表)中,这个常量就对应null值,所以常量池的索引从1而非0开始。以上图为例显示有24个常量,但是根据 java -verbose的输出结果,显示只有23个常量。
- 在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;
- 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;
- 用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之类,如方法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:复制操作数堆栈上的顶部值,并将复制 的值压入操作数堆栈。
先解释到这里吧,感觉还需要学习。