HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
执行编译 javac -parameters -d . HelloWworld.java
,如果加-parameters
这个参数,那么他就会保留方法中参数的名称信息(比如 args
)。
编译为 HelloWorld.class 后是这个样子的,比如在Linux下用od -t xC HelloWorld.class
遍历查看二进制字节码文件内容:
[root@localhost ~]# od -t xC HelloWorld.class
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63
0000160 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f
0000200 57 6f 72 6c 64 3d 01 00 04 6d 61 69 6e 01 00 16
0000220 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72
0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13
0000260 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69
0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61
0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46
0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64
0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
0000440 63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c
0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61
0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f
0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76
0000600 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d
0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a
0000640 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
0000700 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01
0000720 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00
0000740 00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00
0000760 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00
0001000 0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00
0001020 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a
0001040 00 00 00 0a 00 02 00 00 00 06 00 08 00 07 00 0b
0001060 00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00
0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00
0001120 00 00 02 00 14
比如第一列是标号(8进制
),后面的ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
以及下面的都是字节码内容。
根据JVM规范(比如各个厂商实现虚拟机时必须遵守这个规范
),类文件结构如下:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info field[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
可以看到前面的u2 u4
这样的是字节数,比如前四个字节(u4
)是魔术magic
,接下来的两个字节(u2
)是小版本号minor_version
,再接下来两个字节(u2
)是主版本号major_version
等等。
还有下面的constant_pool_count;
和constant_pool[constant_pool_count-1];
是常量池
的信息,access_flags
是访问修饰(比如这个类是公共的还是私有的
),this_class
是这个类自己的包名、类名的信息,super_class
是父类的信息,interfaces_count
和interfaces[interfaces_count]
是指他的接口信息,fields_count
和field[fields_count]
是类中的变量信息,methods_count
和methods[methods_count]
是类中的方法信息,attributes_count;
和attributes[attributes_count]
是指类中的一些附加信息。
魔术
0~3字节,表示他是否是【class】类型的文件。
0000000 ca fe ba be
00 00 00 34 00 23 0a 00 06 00 15 09
其中的ca fe ba be
是魔术代表的四个字节(0~3),魔术是指所有的文件他都有自己的特定的类型,比如Java文件他用了四个字节来表示我这是一个Java文件,而不是其他的文件,不同的文件他有自己的魔术信息,比如你打开一个png的图片和一个jpg的图片他们的魔术信息是不一样的,即魔术就是标识你这个文件到底是啥类型。Java的魔术就是ca fe ba be
(咖啡宝贝,算是Java的冷幽默
)。
版本
0000000 ca fe ba be 00 00 00 34
00 23 0a 00 06 00 15 09
接下来的四个字节 00 00 00 34 代表的是版本。前两个00 00
是minor version
,好像没有在类文件中体现,主要是他的主版本即00 34
(34是16进制,翻译成十进制就是52
),52
就代表JDK8,51
是JDK7,53
是JDK9。总之,编译后的class文件版本是34
的话,就说明是基于JDK8的版本。
常量池
【表1】
Contant Type | Value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
0000000 ca fe ba be 00 00 00 34 00 23
0a 00 06 00 15 09
常量池占据了整个类文件中的相当大的比重。8 ~ 9字节,即00 23
表示常量池长度,00 23
(35)表示常量池有 #1~#34
项(一共有35项),注意#0
项不计入(所以有34项),也没有值。
详细如下
第 #1 项
0a
表示一个method信息,00 06
和00 15
(21)表示他引用了常量池中#6
和#21
项来获得这个方法的【所属类】和【方法名】
0000000 ca fe ba be 00 00 00 34 00 23 0a
00 06 00 15
09
第 #2 项
09
表示一个filed信息,00 16
(22)和00 17
(23)表示他引用了常量池中#22
和#23
项来获得这个成员变量的【所属类】和【成员变量名】
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
0000020 00 16 00 17
08 00 18 0a 00 19 00 1a 07 00 1b 07
第 #3 项
08
表示一个字符串常量名称,00 18
(24)表示它引用了常量池中 #24
项
0000020 00 16 00 17 08 00 18
0a 00 19 00 1a 07 00 1b 07
第 #4 项
0a
表示一个method信息,00 19
(25)和00 1a
(26)表示它引用了常量池中#25
和#26
项来获得这个方法的【所属类】和【方法名】
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a
07 00 1b 07
第 #5 项
07
表示一个class信息,00 1b
(27)表示它引用了常量池中#27
项
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b
07
第 #6 项
07
表示一个class信息,00 1c
(28)表示它引用了常量池中#28
项
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
0000040 00 1c
01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
第 #07 项
01
表示一个utf8串,00 06
表示长度,3c 69 63 69 74 3e
是【< init >】
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e
01 00 03 28 29
第 #08 项
01
表示一个utf8串,00 03
表示长度,28 29 56
是【()V】其实就是表示无参、无返回值
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56
01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
第 #09 项
01
表示一个utf8串,00 04
表示长度,43 6f 64 65
是【Code(方法的一个属性,对应着字节码)】
0000060 56 01 00 04 43 6f 64 65
01 00 0f 4c 69 6e 65 4e
第 #10 项
01
表示一个utf8串,00 0f
(15)表示长度,4c 69 6e 65 4e 75 6d 62 72 54 61 62 6c 65
是【LineNumberTable(方法的属性,方法的行号表)】
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65
01 00 12 4c 6f 63
第 #11 项
01
表示一个utf8串,00 12
(18)表示长度,4c ~ 65
是【LocalVariableTable(代表方法的一个属性,是方法的本地变量表)】
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65
54 61 62 6c 65 01
第 #12 项
01
表示 utf8串,00 04
表示长度,74 68 69 73
是【this】
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73
01 00 1d 4c 63 6e 2f 69 74 63
第 #13 项
01
表示utf8串,00 1d
(29)表示长度,是【Lcom/waca/jvm/t5/HelloWorld;(比如)(这是类型,在字节码里表示类型的话,引用类型是L开头;结尾)】
0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63
0000160 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f
0000200 57 6f 72 6c 64 3d
01 00 04 6d 61 69 6e 01 00 16
第 #14 项
01
表示 utf8串,00 04
表示长度,74 68 69 73
是【main(方法名称,可以和#13
联系起来,是表示哪个类中的main方法)】
0000200 57 6f 72 6c 64 3d 01 00 04 6d 61 69 6e
01 00 16
第 #15 项
01
表示一个utf8串,00 16
(22)表示长度,是【([Ljava/lang/String;)V】括号里面是参数类型,前面再加一个左半边的[,表示他是数组,即其实就是参数为字符串数组,无返回值。
0000200 57 6f 72 6c 64 3d 01 00 04 6d 61 69 6e 01 00 16
0000220 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72
0000240 69 6e 67 3b 29 56
01 00 04 61 72 67 73 01 00 13
第 #16 项
01
表示一个utf8串,00 04
表示长度,是【args(刚才main方法的参数名称)】
0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73
01 00 13
第 #17 项
01
表示一个utf8串,00 13
(19)表示长度,是【[Ljava/lang/String;(进一步描述了刚才的参数类型)】
0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13
0000260 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69
0000300 6e 67 3b
01 00 10 4d 65 74 68 6f 64 50 61 72 61
第 #18 项
01
表示utf8串,00 10
(16)表示长度,是【MethodParameters(方法的一个属性,是指方法的参数名称信息)】
0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61
0000320 6d 65 74 65 72 73
01 00 0a 53 6f 75 72 63 65 46
第 #19 项
01
表示一个utf8串,00 0a
(10)表示长度,是【SourceFile】
0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46
0000340 69 6c 65
01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
第 #20 项
01
表示一个utf8串,00 0f
(15)表示长度,是【HelloWorld.java(#19 #20联系起来就是说这个类的源文件是HelloWorld.java)】
0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
0000360 2e 6a 61 76 61
0c 00 07 00 08 07 00 1d 0c 00 1e
第 #21 项
0c
(12)表示一个【名+类型】,00 07 00 08
引用了常量池中#7 #8
两项。
0000360 2e 6a 61 76 61 0c 00 07 00 08
07 00 1d 0c 00 1e
第 #22 项
07
表示一个class信息,00 1d
(29)引用了常量池中#29
项
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d
0c 00 1e
第 #23 项
0c
表示一个【名+类型】,00 1e
(30)00 1f
(31)引用了常量池中#30 #31
两项。
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
0000400 00 1f
01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64
第 #24 项
01
表示一个utf8串,00 0b
(11)表示长度,是hello world
0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64
第 #25 项
07
表示一个class信息,00 20
(32)引用了常量池中#32
项
0000420 07 00 20
0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
第 #26 项
0c
表示【名+类型】,00 21
(33)00 22
(34)引用了常量池中#33 #34
两项
0000420 07 00 20 0c 00 21 00 22
01 00 1b 63 6e 2f 69 74
第 #27 项
01
表示一个utf8串,00 1b
(27)表示长度,是【com/waca/jvm/t5/HelloWorld(比如)】
0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
0000440 63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c
0000460 6f 57 6f 72 6c 64
01 00 10 6a 61 76 61 2f 6c 61
第 #28 项
01
表示一个utf8串,00 10
(16)表示长度(6a开始到74,即就是Object类),是【java/lang/Object】
0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61
0000500 6e 67 2f 4f 62 6a 65 63 74
01 00 10 6a 61 76 61
第 #29 项
01
表示一个utf8串,00 10
(16)表示长度,即6a到6d,是java/lang/System
0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d
01 00 03 6f
第 #30 项
01
表示一个utf8串,00 03
表示长度,是out
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f
0000540 75 74
01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
第 #31 项
01
表示一个utf8串,00 15
(21)表示长度,即4c到3b,是Ljava/io/PrintStream;
0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
0000560 69 6e 74 53 74 72 65 61 6d 3b
01 00 13 6a 61 76
第 #32 项
01
表示一个utf8串,00 13
(19)表示长度,是java/io/PrintStream
0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76
0000600 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d
第 #33 项
01
表示一个utf8串,00 07
表示长度,是【println】
0000620 01 00 07 70 72 69 6e 74 6c 6e
01 00 15 28 4c 6a
第 #34 项
01
是utf8串,00 15
(21)是长度,是【(Ljava/lang/String;)V】,即类型是String类型,返回值是void
0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a
0000640 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
0000660 29 56
00 21 00 05 00 06 00 00 00 00 00 02 00 01
通过这个小例子,可以看得出常量池里就是记录着我们Java类中的各种信息,包括它的类的信息
、父类的信息
、方法的信息
、成员变量的信息
等等,还有方法的属性
。
访问标识和继承信息
上面的是类文件结构中的魔术
、版本
、常量池
,接下来是access_flags
,这是访问修饰的描述。
0000660 29 56 00 21 00 05 00 06 00 00
00 00 00 02 00 01
到29 56
为止是常量池,从00
开始是access_flag
。
00 21
表示该class是一个类,公共的(21怎么看呢?要看【表2】中哪几项加起来的,比如表中0x0020加0x0001就是21,表示他是一个公共的类
)。
00 05表示根据常量池中去第#5项找到本类全限定名(可以参考为com/waca/HelloWorld.java
)。
00 06
表示根据常量池中去第#6
项找到父类全限定名。(可以参考为java/lang/Object
)。
00 00
表示接口的数量,本类为0(表示没有接口,如果有接口的话,00 00后面还会有一些描述接口的信息
)。
【表2】
FlagName | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public;may be accessed from outside its |
ACC_FINAL | 0x0010 | Declared final;no subclasses allowed |
ACC_SUPER | 0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction(表示类) |
ACC_INTERFACE | 0x0200 | Is an interface,not a class |
ACC_ABSTRACT | 0x0400 | Declared abstract;must not be instantiated |
ACC_SYNTHETIC | 0x1000 | Declared synthetic;not present in the source code(人工合成的,不是源代码的) |
ACC_ANNOTATION | 0x2000 | Declared as an annotation type(注解) |
ACC_ENUM | 0x4000 | Declared as an enum type(枚举) |
field 信息
接下来就是 fields_count
和fields[fields_count]
,即成员变量的信息。
0000660 29 56 00 21 00 05 00 06 00 00 00 00
00 02 00 01
这里的00 00
表示成员变量数量是0。
FieldType | Type | Interpretation |
---|---|---|
B | byte | signed byte |
C | char | Unicode character code point in the Basic Multilingual Plane,encoded with UTF-16 |
D | double | double-precision floating-point value |
F | float | single-precision floating-poiint value |
I | int | integer |
J | long | long integer |
L ClassName; | reference | an instance of class ClassName |
S | short | signed short |
Z | boolean | true or false |
[ | reference | one array dimension(一维数组) |
method信息
0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
从00 02
两个字节开始,这是代表本类中方法的数量,02
说明有两个方法,第一个其实就是那个构造方法
,第二个是main方法
。一个方法由访问修饰符
、名称
、参数描述
、方法属性数量
、方法属性
组成。
0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
0000700 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01
0000720 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00
0000740 00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00
0000760 01 00 00 00 05 00 0c 00 0d 00 00
00 09 00 0e 00
从第一行的最后00 01
开始到最后一行的00 00
为止,是方法信息。
对构造方法的分析
00 01
代表方法的访问修饰(可以参考上面【表2】),01
就是公共方法。
接着00 07
代表方法的名字,07
表示要查询常量池中的#07
项来得到方法的名字(查表可知,他是init方法
)。
00 08
是方法的参数和返回值类型信息,08
表示要查询常量池中的#08
项来获得这些信息(查表可知,无参、返回值是void
)。
00 01
是代表方法属性的数量,即有一个属性,具体是什么属性呢,下面的00 09
,即要查常量池表中的#09
项来得到具体是哪个属性(查表可知,他是code属性,code代表方法体中的代码属性
),那么code属性由哪些部分组成?
接下来的00 00 00 2f
(47)这四个字节代表整个code属性的长度,即有47个字节长度,往后从00 01...00 00
结束为止,这些都是属性的范围,其中00 01
(上面的2f之后)是代表方法的一个操作数栈
的深度,接下来的00 01
代表这个方法的局部变量表
的长度,00 00 00 05
是具体的code属性内代码的一个长度,即往后数5个字节即 2a b7 00 01 b1
就是方法体内的字节码(其实这就是构造方法内部真正要执行的五个字节的代码
),再往后00 00 00 02
表示比如code属性还会有一些子属性,即00 0a
(常量池#10项,是LineNumberTable属性,这个属性就是把我们字节码的行号跟Java源码的行号进行一个对应,主要是为了方便以后debug调试来使用的
)。00 00 00 06
表示LineNumberTable属性的总长度,数6个字节,即00 01 00 00 00 04
,这就是第一个子属性,就是行号对应表的属性,其中00 01
是表的长度,00 00
代表字节码的jichanghao(?),即2a b7 00 01 b1
是第0行,他对应着Java源码中的00 04
即第四行(可以翻一下Java源码),这是第一个属性。接下来00 0b...00 00
是第二个属性,其中的00 0b
也表示查常量池中第#11
项(查表可知,是LocalVariableTable属性,即局部变量表
),局部变量表整个属性的长度是00 00 00 0c
(12),即往后数12个字节,00到最后结束的00为止,其中00 01
表示局部变量表里有1个局部变量,00 00 00 05
表示从字节码的第0行开始开始到字节码的第5行,即这个局部变量的作用范围涵盖了这个方法内的所有行,从第0行到第5行,00 0c
表示局部变量的名字是查常量池中第#12
项(查表可知是this
),即这是表示构造方法里有一个this的局部变量,类型是00 0d
(常量池#13项
)即是Lcom/waca/HelloWorld,即this是当前这个类的类型,最后的00 00
表示他的局部变量表中的槽位号,即第0号(可以看到槽位长度是1,上面提到过“接下来的00 01代表这个方法的局部变量表的长度”
),即这个this占用了槽位中第0号。
对main方法的分析
0000760 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00
0001000 0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00
0001020 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a
0001040 00 00 00 0a 00 02 00 00 00 06 00 08 00 07 00 0b
0001060 00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00
00 09
代表方法的访问修饰符(比public多出了8,即static,所以这是public static
)。
00 0e
是方法的名称(查常量池表就可以知道是main
)。
00 0f
表示方法的类型(查常量表可知参数类型是String[] 返回值类型是void
)。
00 02
是方法属性的个数,这里是2,跟刚才的构造方法相比多出了1个,是因为刚才的构造方法没有参数,而Main方法是有参数,所以他会把这个参数作为一个属性再加入到字节码中。
00 09
是第一个属性,查常量池表就能知道是code属性,就是代表了方法体内的那些代码,00 00 00 37
是整个code属性的总长度,即00 02到 00 00(0001060的最后),这么长的字节都是这个属性的范围,其中00 02
代表操作数栈的最大深度是2,00 01
是代表局部变量表的长度,刚才构造方法的局部变量长度是1,里面存了一个this,而这个main方法虽然他里面没有this,但是他多了一个字符串数组的参数,所以他的局部变量表的长度也是1,00 00 00 09
是方法体内代码的长度,即从b2
到b1
,这就是主方法内具体的代码,当然我们知道主方法内就打印了System.out.println(“hello world”); 其实就长这样b2 00 02 12 03 b6 00 04 b1
。00 00 00 02
是说我有两个附加属性,00 0a
是第一个附加属性,查表可知是LineNumberTable属性,即行号表,00 00 00 0a
是行号表的长度,即10个字节,从00 02...00 07
,行号表里面有两项(00 02
),第一项是说00 00 即字节码中第0行对应着Java源代码中第6行(00 06
),字节码中第8行(00 08
)对应着Java源代码的第7行(00 07
)。第二个属性是00 0b
,查表可知是局部变量表,他的长度是00 00 00 0c
即12,即00 01
到结束的00 00
,其中00 01
表示局部变量里面有1个,他覆盖的范围 00 00 00 09
即9个字节,第0行到第9行(b2 00 02 12 03 b6 00 04 b1
),这是局部变量的作用范围,00 10
查表可知是他的名字是args,00 11
即类型是查常量池
表后可知是string数组类型,最后的00 00
表示他占的是哪个槽位,即占的第0行。值得注意的是,目前为止是这个方法的第一个属性,在前面的00 02
中可以看到这个方法是有两个属性。
0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00
第二个属性是00 12
(#18),查常量池表可知是MethodParameters属性,这是因为我们在编译的时候加了一个 -parameters
这个选项,他就会编译之后会把这个方法参数的名称信息以MethodParameters编译到字节码中,这样我的运行期间就可以反射得到方法的参数名称了,00 00 00 05
表示这个第二个属性的长度是5,然后01 00 10 00 00
这五个字节代表什么意思呢,其中01
代表参数的数量(因为我们只有一个args参数
),00 10
表示参数名字,查常量池知道是args,00 00
表示参数的访问修饰福,这里的00
代表是正常的,如果是10
是代表final的,这是第二个方法的分析。
附加属性
0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00
0001120 00 00 02 00 14
附加属性的字节是00 01 00 13 00 00 02 00 14
,其中00 01
表示有1个附加属性,00 13
(#19)是表示这个附加属性是SourceFile,就是你这个字节码对应的Java源文件的名称,00 00 00 02
表示整个属性的长度是2,所以是最后两个字节00 14
(#20),差常量表的话,即可查出来他的源代码是HelloWorld.java这个文件。
参考文件
https://docs.oracel.com/javase/specs/jvms/se8/html/jvms-4.html
通过这个文档都能查询所有的字节描述的含义。