深入学习Java虚拟机——类文件结构

简介: 推荐书籍《深入理解Java虚拟机》,本文为个人学习笔记,删除一些不必要文字,并加入部分个人理解,日后复习较为简洁易懂

 Java源码由编译器编译为所有平台上的虚拟机都能统一使用的程序存储格式——字节码文件,即 .class文件,通过这种方式,Java语言具备了平台无关性的特点,Class类文件是一组以8位字节为基础单位的二进制流,各个数据项目按照顺序紧凑的排列在Class文件中,中间没有任何分隔符。

1. Class类文件的结构

    1.Class类文件格式:只包含两种数据类型,无符号数和表。

    2.无符号数:属于基本的数据类型,以u1,u2,u4,u8来分别表示1个,2个,4个,8个字节长度的无符号数,比如数字,索引引用,数量值或按照utf-8编码的字符串值。

    3.表:有多个无符号数或者其他表构成的复合数据类型,以“_info”结尾,用于表示有层次关系的复合结构的数据。

    4. Class类文件本身就是一张表,它由17个数据项构成:magic,minor_version,major_version,constant_pool_count,constant_pool,access_flags,this_class,super_class,interfaces_count,interfaces,fields_count,fields,methods_count,methods,attributes_count,attributes。无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,都会使用一个前置的容器计量数加若干个连续数据项的形式,比如attributes_count与attributes,fields_count与fields,methods_count与methods等。

1.1 magic,minor_version,major_version

    1. magic:是一个无符号数类型的数据项,每个Class文件的头4个字节称为魔数,该数据项在每个Class类文件中只有一个,且占据4字节。作用是确定这个文件是否是一个能被虚拟机接受的Class文件。也就是说所有字节码文件的前4个字节是固定的,16进制表示为 0xCAFEBABE。

    2. minor_version和major_version:都是无符号数类型的数据项,在magic之后紧跟的4个字节就是minor_version和major_version,表示当前字节码文件所使用的jdk版本,minor_version表示次版本号,占前两个字节,后两个字节就表示major_version,为主版本号。如 00 00 00 32这一段16进制码,也就是4个字节,前两个字节所表示的十进制数字分别为 minor_version=0x0000=0,后两个字节表示的十进制数字为 major_version=0x0032=50,所以Class文件的jdk十进制版本号数字为  50.0,对应的jdk编译器版本是 jdk 1.6。

1.2 常量池——constant_pool_count,constant_pool

    1. constant_pool_count:是一个无符号数类型的数据项,该数据项紧跟与版本号之后,用于统计常量池中数据项的数量,该数据项只有一个,占用2个字节。该项之后,便紧跟常量池中constant_pool。该数据项的计数是从1开始,而不是0,所以真正常量池中的数据项为 constant_pool_count-1。也就是说常量池中数据项的索引值范围是1~constant_pool_count。

    2. constant_pool:常量池表,紧跟于constant_pool_count之后,这是一个表类型的数据项。常量池是Class文件的资源仓库,是Class文件中与其他数据项关联最多的数据类型,也是占用Class文件空间最大的数据项之一,也是Class文件中出现的第一个表类型数据项。

    3. 常量池所存常量在源码中是什么:主要为字面量和符号引用。字面量如 123 数字,"ss"等这类文本字符串或声明为final的常量值;符号引用包括了三类,分别是常量类和接口的全限定名字段的名称和描述符方法的名称和描述符

    4. 常量池所存储的数据类型分类:常量池中的每一个数据项都是表类型,也就是一个表。每个表的开头都会有一个标志位tag(只占用1个字节),用来指出当前的数据属于哪一个常量类型的数据。具体类型如下

  1. CONSTANT_Utf8_info:表示utf-8编码的字符串,该类型数据项的表的结构中的数据项为 tag,length,bytes三种。tag即该类型常量的标志位,十进制标志值为1紧接着是length,表示utf8编码的字符串所占用的字节数,占用2个字节空间接下来就是bytes,每个bytes占用1个字节空间,length个bytes就是原utf8编码的字符串的二进制码。                                                                                                 注意:由于CONSTANT_Utf8_info型常量被引用与描述Class文件中的方法,字段等,所以方法或字段名的最大长度也就是CONSTANT_Utf8_info的中所存储字节码空间的最大值,最大值即length的最大值就是两个字节的空间,也就是说字符串的最大长度为64kb,如果方法或变量名超过该长度则无法编译。
  2. CONSTANT_Integer_info:表示Integer字面量,该类型数据项的表的结构中的数据项为 tag,bytes。                                                其tag十进制标志值为3;bytes将按照高位在前存储int值,4个字节长度
  3. CONSTANT_Float_info:表示Float型字面量,该类型数据项的表的结构中的数据项为 tag,bytes。                                                    其tag十进制标志值为4;bytes将按照高位在前存储float值,4个字节长度
  4. CONSTANT_Long_info:表示Long字面量,该类型数据项的表的结构中的数据项为 tag,bytes。                                                       其tag十进制标志值为5;bytes按照高位在前存储long值,8个字节长度
  5. CONSTANT_Double_info:表示Double字面量,该类型数据项的表的结构中的数据项为 tag,bytes。                                                      其tag十进制标志值为6;bytes按照高位在前存储long值,8个字节长度
  6. CONSTANT_Class_info:表示类或接口的符号引用,该类型数据项的表的结构中的数据项为 tag,index。                                                      其tag十进制标志值为7;index表示指向全限定名常量项的索引,2个字节长度。
  7. CONSTANT_String_info:表示字符串类型字面量,该类型数据项的表的结构中的数据项为 tag,index。                                              其tag十进制标志值为8;index表示指向字符串字面量的索引,2个字节长度。
  8. CONSTANT_Fieldref_info:表示字段的符号引用,该类型数据项的表的结构中的数据项为 tag,indx,indx。                                              其tag十进制标志值为9;index表示指向声明字段的类或者接口描述符的CONSTANT_Class_info的索引,2个字节长度;index表示指向字段描述符CONSTANT_NameAndType_info的索引,2个字节长度。
  9. CONSTANT_Methodref_info:表示类中方法的符号引用,该类型数据项的表的结构中的数据项为 tag,indx,indx。                                              其tag十进制标志值为10;index表示指向声明方法的类描述符的CONSTANT_Class_info的索引,2个字节长度;index表示指向名称及类型描述符CONSTANT_NameAndType_info的索引,2个字节长度。
  10. CONSTANT_InterfaceMethodref_info:表示接口中方法的符号引用,该类型数据项的表的结构中的数据项为 tag,indx,indx。        其tag十进制标志值为11;index表示指向声明方法的接口描述符的CONSTANT_Class_info的索引,2个字节长度;index表示指向名称及类型描述符CONSTANT_NameAndType_info的索引,2个字节长度。
  11. CONSTANT_NameAndType_info:表示字段或方法的部分符号引用,该类型数据项的表的结构中的数据项为 tag,indx,indx。         其tag十进制标志值为12;index表示指向该字段或方法名称常量项的索引,2个字节长度;index表示指向该字段或方法描述符的索引,2个字节长度。
  12. CONSTANT_MethodHandle_info:表示方法句柄,该类型数据项的表的结构中的数据项为 tag,reference_kind,reference_index。其tag十进制标志值为15;reference_kind的值再1~9之间,只占用1个字节,决定了方法句柄的类型(方法句柄类型的值表示方法句柄的字节码行为);reference_index的值必须是对常量池的有效索引,占用两个字节。
  13. CONSTANT_MethodType_info:标示方法类型,该类型数据项的表的结构中的数据项为 tag,descriptor_index。                             其tag十进制标志值为16;descriptor_index占用两个字节空间,表示方法的描述符,值必须是对常量池的有效索引且必须是CONSTANT_Utf8_info类型结构的数据项。
  14. CONSTANT_InvokeDynamic_info:表示一个动态方法调用点,该类型数据项的表的结构中的数据项为 tag,bootstrap_method_attr_index,name_and_type_index。其tag十进制标志值为18;bootstrap_method_attr_index的值必须是当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引;name_and_type_index,值必须是对常量池的有效索引且必须是CONSTANT_NameAndType_info类型结构的数据项,表示方法名和方法描述符。 

1.3 访问标志——access_flags

    1. access_flags:用于识别一些类或者接口的层次的访问信息,包括这个Class是类还是接口,是否定义为public类型是否定义为abstract类型,是否被声明为final类等。该标志为占用2个字节。具体标示如下

  • ACC_PUBLIC:标志值为0x0001,表示是否为public类型
  • ACC_FINAL:标志值为0x0010,表示是否声明为final,只有类可以
  • ACC_SUPER:标志值为0x0020,表示是否允许使用invokespecial字节码指令
  • ACC_INTERFACE:标志值为0x0200,表示是否为接口
  • ACC_ABSTRACT:标志值为0x0400,表示是否为abstarct类
  • ACC_SYNTHETIC:标志值为0x1000,表示该类不是由用户编写的
  • ACC_ANNOTATION:标志值为0x2000,表示这是一个注解
  • ACC_ENUM:标志值为0x4000,表示这是一个枚举

    2. 计算access_flags:如果一个类是public,但没有其他修饰符那么则该类为ACC_PUBLIC和ACC_SUPER标志位为真,而其他标志位为假,所以标志值为 0x0001|0x0020=0x0021。

1.4 this_class,super_class,interfaces

    1. this_class:类索引,一个占用2个字节的数据。指向常量池中数据项结构类型为CONSTANT_Class_info的一个数据项,通过该数据项中的值找到定义在常量池中数据项结构类型为CONSTANT_Utf8_info中的得数据项,再依据此数据项得到全限定名字符串。

    2. super_class:父类索引,一个占用2个字节的数据。指向常量池中数据项结构类型为CONSTANT_Class_info的一个数据项,通过该数据项中的值找到定义在常量池中数据项结构类型为CONSTANT_Utf8_info中的得数据项,再依据此数据项得到全限定名字符串。

    3. interfaces:接口索引集合,表类型数据项,是一组占用2个字节的数据。接口索引集合入口第一项为接口计数器(interfaces_count),占用2个字节,表示索引表的容量。如果没有实现任何类,则该值为0。如果实现了接口,则后面会紧跟所实现接口的索引,每个索引都占两个字节,与类相似的方式寻找接口的全限定名。

    4. Class文件使用this_class,super_class,interfaces这三项数据来确定当前Class文件所代表的的类的继承关系。类索引用于确认这类的全限定名,父类索引用于确定父类的全限定名,接口索引集合用来描述该类实现的所有接口。

1.5 fields

    1. fields:字段表集合,表类型数据项,用于描述类或接口中声明的字段。字段表中的数据项的结构包括u2类型的access_flags,name_index,descriptor_index,attributes_count数据项以及attribute_info类型的attributes数据项这5个数据项构成了字段表中的单个数据项。

    2. access_flags:该数据项与类中的access_flags数据项相似,都是u2类型。

  • ACC_PUBLIC:标志值为0x0001,表示是否为public类型
  • ACC_FINAL:标志值为0x0010,表示是否声明为final
  • ACC_PRIVATE:标志值为0x0002,表示是否private
  • ACC_PROTECTED:标志值为0x0004,表示是否为protected
  • ACC_STATIC:标志值为0x0008,表示是否为static
  • ACC_TRANSIENT:标志值为0x0080,表示是否为transient
  • ACC_SYNTHETIC:标志值为0x1000,表示是否由编译器自动产生
  • ACC_VOLATILE:标志值为0x0040,表示是否为volatile
  • ACC_ENUM:标志值为0x4000,表示是否为enum

    2. name_index,descriptor_index:对常量池的引用,分别代表字段的简单名称以及字段和方法的描述符。

    3. 字段表集合不会列出从父类中继承而来的字段,但有可能列出原本不存在的字段,比如内部类中会自动添加指向外部类的实例的字段。

1.6 methods

    1. mehtods:方法表集合,表类型的数据项,方法表中的每一个数据项的结构类似于字段表结构,依次包括访问标志(access_flags),名称索引(name_index),描述符索引(descriptor_index),属性表集合(attributes)和attributes_count。

    2. access_flags:

  • ACC_PUBLIC:标志值为0x0001,表示是否为public类型
  • ACC_FINAL:标志值为0x0010,表示是否声明为final
  • ACC_PRIVATE:标志值为0x0002,表示是否private
  • ACC_PROTECTED:标志值为0x0004,表示是否为protected
  • ACC_STATIC:标志值为0x0008,表示是否为static
  • ACC_BRIDGE:标志值为0x0040,表示是否是由编译器产生的桥接方法
  • ACC_SYNTHETIC:标志值为0x1000,表示是否由编译器自动产生
  • ACC_SYNCHRONIZED:标志值为0x0020,表示是否为synchronized方法
  • ACC_NATIVE:标志值为0x0100,表示是否为native方法
  • ACC_STRICTFP: 0x0800,表示是否为strictfp
  • ACC_VARARGS:0x0080,表示是否接受不定参数

    3. 对于方法中的代码:方法的定义可以通过访问标志,名称索引,描述符索引表达清楚,但方法里面代码经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为Code的属性里面。

    4. attributes_count:表示属性表集合中属性的数量

    5. attributes:属性表类型,其中有attributes_count个属性项。

    6. 方法表集合的字节码分析:首先是方法表的入口——methods_count,一个u2类型的数据,代表着类中的方法数量,然后是方法的访问标志值access_flags,接下来就是name_index名称索引,紧接着是描述符索引descriptor_index,接下来就是属性表计数器attributes_count,然后就是属性表中的每一个属性的名称索引。

1.7 attributes

    1. attributes:属性表类型集合,即attribute_info。Class文件,字段表,方法表都可由携带自己属性表集合,用以描述某些场景专有的信息。包括Code属性,ConstantValue属性,Exceptions属性等21个属性。

 

    2. Code属性:方法体中的代码经过编译器编译之后,最终变成字节码指令存储在Code属性之内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,比如接口中的方法或抽象方法就不存在Code属性。

    3.ConstantValue属性:如果一个变量被static和final同时修饰,并且该变量为基本类型或String类型,则会将该变量的字节码放进ConstantValue属性中,如果没有被final修饰或并非基本类型或String变量,则不会放进。

    4. Exceptions属性:列举出方法中可能抛出的受检查异常,也就是方法名后紧跟的throws关键字后列举的异常。

    5.LineNumberTable属性:用于描述Java源码行号与字节码行号之间的对应关系。最主要的用处就是,当程序抛出异常时,可以看到出错的源码行号,并且在调试时,也无法按照源码的行号进行设置断点。

    6.LocalVariableTable属性:用于描述栈桢中局部变量与java源码中定义的变量之间的关系。

    7. InnerClasses属性:用于描述内部类与宿主类的关系。

2. 字节码指令

字节码指令操作的数据类型会有特殊的符号来标记,比如:i 开头的字节码指令代表对int类型的数据操作, l 开头的字节码指令代表对long类型的数据操作,d 开头的字节码指令代表对double类型的数据操作,s 开头的字节码指令代表对short类型的数据操作,b 开头的字节码指令代表对byte或者boolean类型的数据操作,c 开头的字节码指令代表对char类型的数据操作,f 开头的字节码指令代表对float类型的数据操作,a 代表对reference(引用)操作。另外,对于数组对象,只需在每个类型字母的后面加一a,也就是ia,ba,sa等。数据类型字母后面再跟指令操作的字符,比如iload,iaload。

详细内容学习推荐:虚拟机字节码指令

相关文章
|
4天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
8天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
21 2
|
12天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
17天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
18天前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
25 4
|
21天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
21天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
19天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
23天前
|
缓存 Java 程序员
Java|SpringBoot 项目开发时,让 FreeMarker 文件编辑后自动更新
在开发过程中,FreeMarker 文件编辑后,每次都需要重启应用才能看到效果,效率非常低下。通过一些配置后,可以让它们免重启自动更新。
24 0
|
10天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。