水稻:看你研究盯着这个文档一天了,什么玩意让人心驰神往
菜瓜:前几天意外得到一本武功秘籍《jvms8》,看起来就情不自禁
水稻:这不是Java虚拟机的说明文档吗<PS:投来惊吓的目光>
菜瓜:是的,在研究第四章-The class File Format. 讲的是class文件结构。以前模糊的知道我们写的java代码是以二进制字节码加载到虚拟机然后执行的,但是没有见识过
水稻:有什么收获,分享一下啊
菜瓜:只是在研究,可以一起探讨。我是这么干的,先准备工具
菜瓜:写一段最简单的demo
package club.interview.jvm; /** * @author QuCheng on 2020/7/10. */ public class ClassOriginal { }
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
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]; // 表示文件信息,譬如路径和文件名 }
水稻: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 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区域只有一个方法就是默认的构造方法
水稻:有收获,虽然对写代码没啥太大用,但是这个流程搞清楚了就比较通透
菜瓜:后面我还想继续深入一下,有收获再分享啊
水稻:可以可以
总结: