本文部分摘自《深入理解 Java 虚拟机第三版》

概述

我们知道,Java 具有跨平台性,其实现基础就是虚拟机和字节码存储格式。Java 虚拟机不与 Java 语言绑定,只与 Class 文件所关联。Java 虚拟机作为一个通用的、与机器无关的执行平台,任何语言都可以将 Java 虚拟机作为它们的运行基础,以 Class 文件作为它们产品的交付媒介。

Class 文件是一组以 8 个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个 Class 文件中存储的内容几乎全部是程序运行的必要数据。当遇到需占用 8 个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个 8 个字节进行存储。

Class 文件中有两种数据类型,分别是无符合数和表:

  • 无符号数属于基本数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或 UTF-8 编码构成字符串值
  • 表是由多个无符号数或其他表作为数据项构成的复合数据类型,一般以 _info 结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件本质上也可以视作是一张表

无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的集合。

下面是 Class 文件格式:

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count - 1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attribute_count 1
attribute_info attributes attributes_count

魔数和 Class 文件版本

Class 文件的头 4 个字节被称为魔数(Magic Number),它的唯一作用是确定该 Class 文件是否能被虚拟机接受,其值为 0xCAFEBABE(咖啡宝贝)。

紧接着魔数的 4 个字节存储的是 Class 文件的版本号:第 5 和第 6 个字节是次版本号(Minor Version),第 7 和第 8 个字节是主版本号(Major Version)。Java 版本号从 45 开始,以后每个 JDK 大版本发布则主版本号加 1。高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件。

常量池

紧接着主、次版本号的是常量池入口,常量池入口需要放置一项 u2 类型的数据,代表常量池容量计数值(constant_pool_count),这个容量计数是从 1 而不是从 0 开始,第 0 项用于表达“不引用任何一个常量池项目”的含义。Class 文件结构中只有常量池的容量计数是从 1 开始,其他都是从 0 开始。

常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近 Java 语言层面的常量概念,如文本字符串、被声明为 final 的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:

  • 被模块导出或开放的包(Package)
  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符
  • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
  • 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

Java 会在虚拟机加载 Class 文件的时候进行动态连接,将符号引用转换为真正的内存入口。常量池中每一项常量都是一个表,最初有 11 种结构不同的表结构数据,后来为了更好地支持动态语言调用,额外增加了 4 种动态语言相关的常量,后来为了支持 Java 模块化,又加入了 2 个常量,所以截止 JDK13,常量表中有 17 种不同类型的常量。这 17 类表都有一个共同的特点,表结构起始的第一位是个 u1 类型的标志位(tag)

17 种常量类型所代表的具体含义如表:

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8 编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 表示方法类型
CONSTANT_Dynamic_info 17 表示一个动态计算常量
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点
CONSTANT_Moudle_info 19 表示一个模块
CONSTANT_Package_info 20 表示一个模块中开放或者导出的包

访问标志

常量池结束之后,紧接着的 2 个字节代表访问标志(access_flags),这个标志用于标识一些类或者接口层次的访问信息,包括这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等等。具体的标志位以及标志的含义如表:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为 Public 类型
ACC_FINAL 0x0010 是否被声明为 final,只有类可以设置
ACC_SUPER 0x0020 是否允许使用 invokespecial 字节码指令的新语义
ACC_INTERFACE 0x0200 标志这是一个接口
ACC_ABSTRACT 0x0400 是否为 abstract 类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC 0x1000 标志这个类并非由用户代码产生
ACC_ANNOTATION 0x2000 标志这是一个注解
ACC_ENUM 0x4000 标志这是一个枚举

access_flags 中一共有 16 个标志位可以使用,当前只定义了其中 9 个,没有使用到的标志位要求一律为零。

类索引、父类索引与接口索引集合

类索引(this_class)、父类索引(super_class)和接口索引(interfaces)都按顺序排列在访问标志之后,类索引和父类索引用两个 u2 类型的索引值表示,而接口索引是一组 u2 类型的数据的集合。

类索引用于确定该类的全限定名,父类索引确定该类的父类的全限定名,由于 Java 不允许多继承,因此父类索引只有一个,Object 类的父类索引为 0。类索引和父类索引各自指向一个 CONSTANT_Class_info 的类描述符常量,通过这个索引值可以找到定义在 CONSTANT_Utf8_info 类型的常量中的全限定名字符串。

对于接口索引集合,入口的第一项 u2 类型的数据为接口计数器(interfaces_count),表示索引表的容量,如果该类没有实现任何接口,则该计数器的值为 0,后面接口的索引表不再占用任何字节。

字段表集合方法表集合

字段表集合(field_info)用于描述接口或类中声明的变量,包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段包含待信息有字段的作用域(public、private、protected)、是实例变量还是类变量(static)、可变性(final)等等。这些信息要么有,要么没有,很适合用标志位来表示,而字段叫什么,被定义为什么数据类型,这些都无法固定,只能用常量池中的常量来描述。

字段表结构如下:

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

access_flags 用来标识字段修饰符(public、static、final、volatile ...),name_index 和 descriptor_index 都是对常量池的引用,分别代表字段的简单名称以及字段和方法的描述符。之后的是属性表集合,用于存储一些额外信息。

字段表集合不会列出从父类或父接口继承而来的字段,但有可能出现原本 Java 代码中没有的字段,例如内部类为了保持对外部类的访问性,编译器会自动添加指向外部类实例的字段。

方法表集合与字段表集合几乎完全一致,仅在标志和属性表集合的可选项中有所区别。至于方法里面的代码,则经编译后存放在方法属性表集合中一个名为 Code 的属性里面。

最新文章

  1. 简单的方向传感器SimpleOrientationSensor
  2. Oracle(DML)
  3. [Android] 解析android framework下利用app_process来调用java写的命令及示例
  4. Linux Shell 文本处理工具集锦
  5. 使用git管理代码的心得
  6. Codeforces 86C Genetic engineering(AC自动机+DP)
  7. -_-#【RequireJS】Define a Module
  8. Java学习-----单例模式
  9. PHP String
  10. .NET技术
  11. 数据绑定以及Container.DataItem几种方式与使用方法分析
  12. 隐藏AutoCompleteTextView下拉框的滚动条
  13. 开启本地MySql数据库远程连接
  14. JS之数组的几个不 low 操作
  15. NodeJs 设置跨域后页面全部变成了源码在浏览器上显示
  16. Linux系统平均负载3个数字的含义
  17. hash与平衡二叉树的区别
  18. js便签笔记(12)——浏览TOM大叔博客的学习笔记 part2
  19. EditText中文文档
  20. linux下配置SS5(SOCK5)代理服务

热门文章

  1. 第7.15节 Python中classmethod定义的类方法详解
  2. Python的富比较方法__eq__和__ne__之间的关联关系分析
  3. 第15.6节 PyQt5安装与配置
  4. PyQt(Python+Qt)学习随笔:QTableView的sortingEnabled属性
  5. Python & PyQt学习随笔:PyQt主程序的基本框架
  6. Linq to SQL 语法整理(子查询 & in操作 & join )
  7. 团队作业4-Day4
  8. 部署基于.netcore5.0的ABP框架后台Api服务端,以及使用Nginx部署Vue+Element前端应用
  9. 关于svg格式问题
  10. python协程需要注意的