水稻:看你研究盯着这个文档一天了,什么玩意让人心驰神往
菜瓜:前几天意外得到一本武功秘籍《jvms8》,看起来就情不自禁
水稻:这不是Java虚拟机的说明文档吗
菜瓜:是的,在研究第四章-The class File Format. 讲的是class文件结构。以前模糊的知道我们写的java代码是以二进制字节码加载到虚拟机然后执行的,但是没有见识过
水稻:有什么收获,分享一下啊
菜瓜:只是在研究,可以一起探讨。我是这么干的,先准备工具
- jvms8官方文档下载 - (因为我是用jdk8编译的,所以下载的是8版本的。可以选择自己的版本 https://docs.oracle.com/javase/specs/index.html)
- idea插件jclasslib Bytecode viewer - (代替javap命令直接在idea中查看字节码编译内容)
- sublime - 查看16进制字节码,方便阅读 (也可以下载idea插件BinEd)
菜瓜:写一段最简单的demo
- java源文件
-
package club.interview.jvm; /** * @author QuCheng on 2020/7/10. */ public class ClassOriginal { }
-
- 二进制 - 编译成class文件
-
11001010 11111110 10111010 10111110 00000000 00000000 00000000 00110100 00000000 00010000 00001010 00000000 00000011 00000000 00001101 00000111 00000000 00001110 00000111 00000000 00001111 00000001 00000000 00000110 00111100 01101001 01101110 01101001 01110100 00111110 00000001 00000000 00000011 00101000 00101001 01010110 00000001 00000000 00000100 01000011 01101111 01100100 01100101 00000001 00000000 00001111 01001100 01101001 01101110 01100101 01001110 01110101 01101101 01100010 01100101 01110010 01010100 01100001 01100010 01101100 01100101 00000001 00000000 00010010 01001100 01101111 01100011 01100001 01101100 01010110 01100001 01110010 01101001 01100001 01100010 01101100 01100101 01010100 01100001 01100010 01101100 01100101 00000001 00000000 00000100 01110100 01101000 01101001 01110011 00000001 00000000 00100010 01001100 01100011 01101100 01110101 01100010 00101111 01101001 01101110 01110100 01100101 01110010 01110110 01101001 01100101 01110111 00101111 01101010 01110110 01101101 00101111 01000011 01101100 01100001 01110011 01110011 01001111 01110010 01101001 01100111 01101001 01101110 01100001 01101100 00111011 00000001 00000000 00001010 01010011 01101111 01110101 01110010 01100011 01100101 01000110 01101001 01101100 01100101 00000001 00000000 00010010 01000011 01101100 01100001 01110011 01110011 01001111 01110010 01101001 01100111 01101001 01101110 01100001 01101100 00101110 01101010 01100001 01110110 01100001 00001100 00000000 00000100 00000000 00000101 00000001 00000000 00100000 01100011 01101100 01110101 01100010 00101111 01101001 01101110 01110100 01100101 01110010 01110110 01101001 01100101 01110111 00101111 01101010 01110110 01101101 00101111 01000011 01101100 01100001 01110011 01110011 01001111 01110010 01101001 01100111 01101001 01101110 01100001 01101100 00000001 00000000 00010000 01101010 01100001 01110110 01100001 00101111 01101100 01100001 01101110 01100111 00101111 01001111 01100010 01101010 01100101 01100011 01110100 00000000 00100001 00000000 00000010 00000000 00000011 00000000 00000000 00000000 00000000 00000000 00000001 00000000 00000001 00000000 00000100 00000000 00000101 00000000 00000001 00000000 00000110 00000000 00000000 00000000 00101111 00000000 00000001 00000000 00000001 00000000 00000000 00000000 00000101 00101010 10110111 00000000 00000001 10110001 00000000 00000000 00000000 00000010 00000000 00000111 00000000 00000000 00000000 00000110 00000000 00000001 00000000 00000000 00000000 00000110 00000000 00001000 00000000 00000000 00000000 00001100 00000000 00000001 00000000 00000000 00000000 00000101 00000000 00001001 00000000 00001010 00000000 00000000 00000000 00000001 00000000 00001011 00000000 00000000 00000000 00000010 00000000 00001100
-
- 16进制
-
cafe babe 0000 0034 0010 0a00 0300 0d07 000e 0700 0f01 0006 3c69 6e69 743e 0100 0328 2956 0100 0443 6f64 6501 000f 4c69 6e65 4e75 6d62 6572 5461 626c 6501 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 0100 0474 6869 7301 0022 4c63 6c75 622f 696e 7465 7276 6965 772f 6a76 6d2f 436c 6173 734f 7269 6769 6e61 6c3b 0100 0a53 6f75 7263 6546 696c 6501 0012 436c 6173 734f 7269 6769 6e61 6c2e 6a61 7661 0c00 0400 0501 0020 636c 7562 2f69 6e74 6572 7669 6577 2f6a 766d 2f43 6c61 7373 4f72 6967 696e 616c 0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 7400 2100 0200 0300 0000 0000 0100 0100 0400 0500 0100 0600 0000 2f00 0100 0100 0000 052a b700 01b1 0000 0002 0007 0000 0006 0001 0000 0006 0008 0000 000c 0001 0000 0005 0009 000a 0000 0001 000b 0000 0002 000c
-
- 引用jvms8中的说明 - 虚拟机按照这个结构体对二进制文件进行解析
-
A class file consists of a single ClassFile structure: //单个class文件结构组成
ClassFile { u4 magic; // 前4字节magic u2 minor_version; // jdk小版本 u2 major_version; // 主要版本 u2 constant_pool_count; // 常量池大小 cp_info constant_pool[constant_pool_count-1]; // 常量池信息
u2 access_flags; // 类访问修饰符 u2 this_class; // 当前class指向常量池 u2 super_class; // 父类class指向常量池 u2 interfaces_count; // 接口总数 u2 interfaces[interfaces_count]; // 接口索引 - 若无接口,则无需统计 u2 fields_count; // 字段统计 field_info fields[fields_count]; // 字段信息 - 若无成员字段,则无需统计 u2 methods_count; // 方法统计 method_info methods[methods_count]; // 方法信息 - 默认会有无参构造 u2 attributes_count; // 属性 attribute_info attributes[attributes_count]; // 表示文件信息,譬如路径和文件名 } - u4、u2 -- u表示无符号位,4和2表示字节数
-
水稻:cafe babe 眼熟 ,0034转换为10进制是52,代表1.8版本。后面的倒是没见过
菜瓜:那我们看一拿着前面一段来对照看看
-
cafe babe 0000 0034 0010 0a00 0300 0d07 按照结构体来划分 u4 4个字节 <cafe babe> 头文件校验 u2 2个字节 <0000> minor_version小版本=0 u2 2个字节 <0034> major_version大版本 52(16进制) = 1.8 u2 2个字节 <0010> 常量池大小 16(16进制)
水稻:懂,后面的 0a00 0300 0d07 是什么呢? 按照结构体的排序是cp_info 常量池数组根据下标编排的内容对吧
菜瓜:没错,这里的cp_info 结构体有一个参照表如下
-
Constant_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 info 也是一个对象,不同对象属性还不一样 // tag=10 方法引用 CONSTANT_Methodref_info { u1 tag; u2 class_index; // class下标 u2 name_and_type_index; // } // tag = 7 CONSTANT_Class_info { u1 tag; u2 name_index; // class索引 } // tag = 1 CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
- 再来看上面没解析的 0a00 0300 0d07以及后续
-
0a00 0300 0d07 u1 1字节 0a 表示10 对应10号结构体 CONSTANT_Methodref_info { u1 tag; u2 class_index; // class下标 u2 name_and_type_index; // 结构体下标 } u1 0a tag 结构体cp_info标识 u2 00 03 class_index 类文件下标,此处指向常量池03位 u2 00 0d name_type_index 指向常量池13位
-
- 再列举几个,你应该就能看明白了
-
衔接第一排0d07 000e 0700 0f01 0006 3c69 6e69 743e 0100 0d已经被使用,从07开始 对照cp_info 07号结构体 // tag = 7 CONSTANT_Class_info { u1 tag; u2 name_index; // class索引 } <07 000e> u1 07 u2 000e 指向常量池第14位 <0700 0f> u1 07 u2 000f 常量池第15位 下一位01 对应结构体 // tag = 1 CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } <01 0006 3c69 6e69 743e> u1 tag u2 0006 后续字节长度 u1 长度为6的字节数组 (3c69 6e69 743e) 对照ASCII表翻译成字符("<init>")
-
水稻:妙啊!你怎么证明是这样的
菜瓜:不慌,用到第二个idea插件工具jclasslib ... 怎么使用我就不演示了。(也可以使用javap命令)编译结果如下
水稻:原来是这样解析的!!
菜瓜:当然这个其实没什么技术含量,只是一个比较死板的解析过程而已,不过这个设计真让人拍案叫绝,只要最后java文件能被编译成这种class文件格式,jvm就都能解析。后面有个区域需要熟悉一下:methods
水稻:哦?有什么讲究
菜瓜:我们方法的执行逻辑都在这里,有个i++和++i的常见面试题可以从这里一探究竟。因为我写的这个demo比较简单,此处的mthods区域只有一个方法就是默认的构造方法
- 要想看懂这个,还得拿jvms8的指令集对照表查看- 第6章6.5
- aload_0 将this从局部变量表加载到操作数栈栈顶 (aload_0指令 - 16进制码是2a)
- invokespecial 调用方法 - 这里是调用的object的构造方法 (invokespecial指令 - 16进制码是b7)
- return - 返回结构 (return - 16进制是 b1)
- 后面标注了指令对应的16进制码,可以呼应上面我们的16进制对照表
- 2a b7 b1
水稻:有收获,虽然对写代码没啥太大用,但是这个流程搞清楚了就比较通透
菜瓜:后面我还想继续深入一下,有收获再分享啊
水稻:可以可以
总结:
- 了解.java文件被javac指令编译后的字节码是如何解析的
- 文中demo过于简单,此处只是为了演示方便。上面有提到i++和++i的面试题,大伙可以自己编译去看看(https://www.cnblogs.com/nightOfStreet/p/13253792.html)