首先准备好java的测试程序.demo如下
经过执行javac Test.java命令,生成了Test.class的文件的16进行文件如下:
cafe babe 0000 0034 0033 0a00 0b00 1c09
000a 001d 0800 1e09 000a 001f 0900 2000
2109 000a 0022 0a00 0a00 230a 0024 0025
0a00 2600 2707 0028 0700 2901 0001 6101
0001 4901 0001 6201 0012 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 0100 0163
0100 063c 696e 6974 3e01 0003 2829 5601
0004 436f 6465 0100 0f4c 696e 654e 756d
6265 7254 6162 6c65 0100 046d 6169 6e01
0016 285b 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 2956 0100 0361 6464 0100
1728 4949 294c 6a61 7661 2f6c 616e 672f
496e 7465 6765 723b 0100 083c 636c 696e
6974 3e01 000a 536f 7572 6365 4669 6c65
0100 0954 6573 742e 6a61 7661 0c00 1100
120c 000c 000d 0100 0131 0c00 0e00 0f07
002a 0c00 2b00 2c0c 0010 000d 0c00 1700
1807 002d 0c00 2e00 2f07 0030 0c00 3100
3201 0015 636f 6d2f 6a76 6d2f 7072 6163
7469 6365 2f54 6573 7401 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0100 106a
6176 612f 6c61 6e67 2f53 7973 7465 6d01
0003 6f75 7401 0015 4c6a 6176 612f 696f
2f50 7269 6e74 5374 7265 616d 3b01 0013
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d01 0007 7072 696e 746c 6e01 0015
284c 6a61 7661 2f6c 616e 672f 4f62 6a65
6374 3b29 5601 0011 6a61 7661 2f6c 616e
672f 496e 7465 6765 7201 0007 7661 6c75
654f 6601 0016 2849 294c 6a61 7661 2f6c
616e 672f 496e 7465 6765 723b 0021 000a
000b 0000 0003 0002 000c 000d 0000 0001
000e 000f 0000 000c 0010 000d 0000 0004
0001 0011 0012 0001 0013 0000 0030 0002
0001 0000 0010 2ab7 0001 2a03 b500 022a
1203 b500 04b1 0000 0001 0014 0000 000e
0003 0000 0003 0004 0005 0009 0007 0009
0015 0016 0001 0013 0000 002a 0003 0001
0000 000e b200 05b2 0006 08b8 0007 b600
08b1 0000 0001 0014 0000 000a 0002 0000
000c 000d 000d 0009 0017 0018 0001 0013
0000 0025 0002 0003 0000 0009 1a1b 603d
1cb8 0009 b000 0000 0100 1400 0000 0a00
0200 0000 1000 0400 1100 0800 1900 1200
0100 1300 0000 1d00 0100 0000 0000 0506
b300 06b1 0000 0001 0014 0000 0006 0001
0000 0009 0001 001a 0000 0002 001b
今天主要是详细讲解这块字节码的的如何组织的,
按照JVM的虚拟机规范中一步一步来.首先看下一个Class文件的主要包含的结构如下:
首先开始4个字节表示魔数(magic)0xcafebabe,以此判断是不是java的字节码文件.
接下来的4个字节表示java的主要版本好号和次版本号.
下面这个表格表示java主版本号以及次版本号的搭配的种类.
JVM规范中说明了一点,主要版本号是45-55,次版本号可以是任意值,我们这里主版本号是52(0x34),次版本号是0,也符合的.
从8,9两个字节是表示常量池的大小,这里常量池是0x33,也就是有51一个常量元素
所有的常量池都是一下通用结构.
从上图可以看到一个常量池元素是的tag是10,参考下图表格可以知道这个元素
是表示CONSTANT_Methodref,
这里表示CONSTANT_Methodref的结构,主要第一个字节表示tag,2,3两个字节表示类索引,4,5两个字节表示名称和类型的索引.
这里分析第1个常量池的元素的结果, 也是#1=#11,#28(#后面表示常量池的
元素索引).
我们可以用javap -verbose Test去验证一下是否分析正确,从下图可以看出,
这里是java.lang.Object类的init方法初始化,
下图表示了第2个常量池元素的存储.这里表示Test的a的变量.
第三个元素的tag表示8表示CONSTANT_String_info,存储结构如下图:
下图表示了第3个常量池元素的存储.这里表示Test的b的变量的值在常量池的索引是30(这里可以在后面解析到索引号为30的时候,就可以看出来索引号30里面存储的是值,就是b变量的值)
下图表示了第4个常量池元素的存储.这里表示类Test的b的变量.
下图表示了第5个常量池元素的存储.这里表示类System类的PrintStream的out的变量.
下图表示了第6个常量池元素的存储.这里表示Test类的c的变量
下图表示了第7个常量池元素的存储.这里表示Test类的add的方法表示
下图表示了第8个常量池元素的存储.这里表示PrintStream类的println的方法表示.
下图表示了第9个常量池元素的存储.这里表示Integer类的valueOf的方法表示
接下来第10个元素是tag=7,表示Class的类结构如下
下图表示了第10个常量池元素的存储.这里表示com/jvm/practice/Test的Class类.
下图表示了第11个常量池元素的存储.这里表示java/lang/Object的Class类.
接下来第12个元素的tag=1,CONSTANT_Utf8表示UTF-8的格式的字符串
下图表示了第12个常量池元素的存储.这里表示变量a
下图表示了第13个常量池元素的存储.这里表示int类型I.
下图表示了第14个常量池元素的存储.这里表示变量b
下图表示了第15个常量池元素的存储.这里表示Ljava/lang/String;
下图表示了第16个常量池元素的存储.这里表示变量c
下图表示了第17个常量池元素的存储.这里表示类默认的初始化函数
下图表示了第18个常量池元素的存储.这里表示void的返回JVM里面表示 ()V
下图表示了第19个常量池元素的存储.这里表示Code
下图表示了第20个常量池元素的存储.这里表示LineNumberTable
下图表示了第21个常量池元素的存储.这里表示main函数名.
下图表示了第22个常量池元素的存储.这里表示main函数的参数类型表示([Ljava/lang/String;)V
下图表示了第23个常量池元素的存储.这里表示add函数的名称.
下图表示了第24个常量池元素的存储.这里表示add函数的参数类型表示
(II)Ljava/lang/Integer
下图表示了第25个常量池元素的存储.这里表示静态变量,函数和静态块初始化函数
下图表示了第26个常量池元素的存储.这里表示SourceFile.
下图表示了第27个常量池元素的存储.这里表示Test.java(java类名称)
接下来28号元素的tag=12,表示CONSTANT_NameAndType_info的结构如下
下图表示了第28个常量池元素的存储.这里表示#28 = NameAndType #17:#18 //前面解析的""(索引17好元素) ()V(索引18号元素)
下图表示了第29个常量池元素的存储.这里表示#29 = NameAndType #12:#13 //对应 a:I
下图表示了第30个常量池元素的存储.这里表示常量1
下图表示了第31个常量池元素的存储.这里对应#31 = NameAndType #14:#15 // b(索引号14):Ljava/lang/String;(索引号15)
下图表示了第32个常量池元素的存储.这里对应#32 = Class #42 // java/lang/System(后面24号常量)
下图表示了第33个常量池元素的存储.这里对应#33 = NameAndType #43:#44 // out:Ljava/io/PrintStream;
下图表示了第34个常量池元素的存储.这里对 #34 = NameAndType #16:#13 // c:I
下图表示了第35个常量池元素的存储 #35 = NameAndType #23:#24 //对应add:(II)Ljava/lang/Integer;
下图表示了第36个常量池元素的存储, 对应#36 = Class #45 // java/io/PrintStream
下图表示了第37个常量池元素的存储, 对应 #37 = NameAndType #46:#47 // println:(Ljava/lang/Object;)V
下图表示了第38个常量池元素的存储, 对应#38 = Class #48 // java/lang/Integer
下图表示了第39个常量池元素的存储, #39 = NameAndType #49:#50 // valueOf:(I)Ljava/lang/Integer;
下图表示了第40个常量池元素的存储, #40 = Utf8 //com/jvm/practice/Test
下图表示了第41个常量池元素的存储 #41 = Utf8 //java/lang/Object
下图表示了第42个常量池元素的存储 #41 = Utf8 #42 = Utf8 java/lang/System
下图表示了第43个常量池元素的存储 #43 = Utf8 // out
下图表示了第44个常量池元素的存储, #44 = Utf8 Ljava/io/PrintStream;
下图表示了第45个常量池元素的存储, #45 = Utf8,java/io/PrintStream
下图表示了第46个常量池元素的存储, #46 = Utf8 println
下图表示了第47个常量池元素的存储, #47 = Utf8 (Ljava/lang/Object;)V
下图表示了第48个常量池元素的存储, #48 = Utf8 java/lang/Integer
下图表示了第49个常量池元素的存储, #49 = Utf8 valueOf
下图表示了第50个常量池元素的存储, #50 = Utf8 (I)Ljava/lang/Integer;
到这里常量池的50个元素都解析完了
以上就是对50个常量池的字节码的描述
接下来是Classs的表示
首先看下access_flags的表示:
首先是access_floags的解析,开始两个字节表示access_flags=0x0021,这个是
个组合,ACC_public 和 ACC_Super组合起来,表示类是公开的以及初始化调用
父类的初始化方法.
this_class表示是常量池的索引是10, 就是com/jvm/practice/Test
接下来是描述的super_class,描述父类是常量池的索引是11,就是
java/lang/Object,
然后是interface_count,表示接口数量是0.
再就是描述fields_count表示属性的属性个数是3
接下里是每一个field_info(属性字段)的结构
属性的访问标志的表格.
第一个属性是access_flags=0x0002,表示ACC_PRIVATE表示private字段,
name_index=13(表示a),discriptor_index=13(类型I) attributes_count为0
第二个属性是access_flags=0x0001,表示ACC_PUBLIC表示public字段,
name_index=14(表示b),discriptor_index=15(内容是Ljava/lang/String) attributes_count为0
第三个属性是access_flags=0x000c是ACC_PUBLIC和ACC_STATIC的组合,表示ACC_PUBLIC表示protected,static字段,
name_index=16(表示c),discriptor_index=13(内容是I) attributes_count为0
接下来描述的是methods_count,表示方法的数量.
从下图可以看出该程序的methods_count=0x0004,表示程序有四个方法,可以从我前面的demo程序中看出只有2个方法,那么还有2个方法是来自哪里的,接着往下看.
下图是描述methods的数据结构.
方法的access_flags的存储表示的访问级别.
首先看下第一个方法,access_flags=0x0001, 对应上图表格中表示ACC_PUBLIC
表示public方法.name_index=17(对应常量池方法,是类对象默认初始化方法),descriptor_index=18(对应常量池为 "()V" ),
attributes_count=0x0001,表示attributes的数量是1个.
下图是attributes的数据结构.
下图是attributes的存储的数据对应的代表属性的含义.
这里的attribute_name_index=19(对应常量池#19 = Utf8 内容是“Code“),从
上图表示Code属性,下图是Code属性的数据结构
接来下code_length=16,这个是JVM的操作指令.第一条指令opcode=0x2a,加载本地变量0(这里0号存储的this指针)号索引值加载到操作数栈中,
第二条指令opcode(操作吗)=是0xb7,表示invokesecial,操作数是0x0001,
可以先看下invokespecial具体介绍,invokesecial表示实例初始化也包括父类的初始化,操作
数Ox0001,对应常量池的是java/lang/Object."":()V, 也就是先执行Object的init方法.
第三条指令opcode=0x2a,加载本地变量0(这里0号存储的this指针)号索引值加载到操作数栈中,(这条指令和第一条是一样的操作)
第四条指令opcode=0x03,表示指令iconst_0,
该指令表示将常量0推送到操作数栈的栈顶.
第五条指令opcode=0x05,操作数(占2个字节)是常量池索引0x0002=2(对应常量池Fieldref #10.#29 // com/jvm/practice/Test.a:I),给a变量赋值为0
0x05代表的是putField指令,对一个字段进行赋值.
第六条指令opcode=0x2a.跟第一条指令一样功能.
第七条指令opcode=0x12,就是ldc指令,后面一个字节操作的常量池索引为0x03,对应是常量池#30的内容为"1"
ldc的指令的含义是将常量池的值加载都操作数栈顶.
第八条指令opcdoe=0xb5, 操作数占2个字节=0x0004, 对应常量池中是 #4 = Fieldref #10.#31 // com/jvm/practice/Test.b:Ljava/lang/String;
putfield指令就是对于Field字段b, 将操作数栈顶的value “1”赋值给变量b.
第九条指令,opcode=0xb1,表示return指令,直接返回
return指令代表方法返回void,到这里code的长度为16的指令代码解析完了
按照attribute的结构格式,接下来是表示两个字节表示
exception_tablea_lengh,从图上可以看到这个异常表的
长度为0,这个属性记录异常表的处理的指令地址,然后下面两个字节代表attributes_count=0x0001,表示方法的附加属性,attribute_name_index=0x0014(索引号是20),对应常量池的索引是LineNumberTable
下图是LineNumberTable的数据结构表示
LineNumberTable的长度是14, 行数表有3行.
下图表示三行指令和java源代码的行数的映射关系
以上是描述第一个类初始化的的方法的
还剩下三个方法没有解析,读者如果明白第一个方法的格式, 后面就是依葫芦画瓢,其实除了java的定义好的两个方法,还有类默认的init方法,还有就是对于的static字段的生成默认clinit方法, 对应指令就是对静态字段进行 赋值,可以通过javap -v -p Test,验证这一点
总结
今天对于CLass的字节码的解析有一个完成描述和对于字节码的对应关系,希望对JVM的深入打下一个良好的基础