【JVM进阶之路】十一:Class文件结构

简介: 【JVM进阶之路】十一:Class文件结构

     

Java虚拟机和Class文件是Java实现系统无关性的基石。

Class文件是JVM实现语言无关性的基石。

image.png

Class文件中包含了Java虚拟机指令集、符号表以及若干其他辅助信息。

每一个 Class 文件对应于一个如下所示的 ClassFile 结构体:

ClassFile {
 u4 magic;
 u2 minor_version;
 u2 major_version;
 u2 constant_pool_count;
 cp_info constant_pool[constant_pool_count-1];
 u2 access_flags;
 u2 this_class;
 u2 super_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];
}

简单看一下各项的含义:

image.png

由于 Class 文件结构没有任何分隔符,所以无论是每个数据项的的顺序还是数量,都是严格限定的,哪个字节代表什么含义,长度多少,先后顺序如何,都是不允许改变的。

接下来我们来具体学习每项的含义。

1、魔数

这是基本上每个Java开发人员的第一个Java程序:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

我使用的是Idea工具,运行,target目录下会生成对应的class文件,为了查看文件的十六进制信息,我们可以安装一个插件HexView

image.png

安装完成之后,选择class文件,右键 HexView,打开后的十六进制如下:

image.png

第一行中有一串特殊的字符 cafebabe,它就是一个魔数,是 JVM 识别 class 文件的标志,JVM 会在验证阶段检查 class 文件是否以该魔数开头,如果不是则会抛出 ClassFormatError

这段字节很有意思——咖啡宝贝,Java原来不止是咖啡,还是宝贝😂

2、版本号

紧跟着魔数后面的四个字节 0000 0031 存储的是 class 文件的版本号:第 5 和第 6 个字节是次版本号(Minor Version),第 7 和第 8 个字节是主版本号(Major Version)。

Java的版本号是从 45 开始的,JDK1.1 之后的每个 JDK 大版本发布主版本号向上加1(JDK1.0JDK1.1使用了45.045.3的版本号),高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件,即使文件格式未发生变化。

0000 0031 对应的十进制为49,是JDK8的内部版本号。

3、常量池

紧接着主、次版本号之后的是常量池入口。

由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。与Java中语言习惯不同,这个容量计数是从1而不是0开始的。

image.png

如图所示,常量池容量为十六进制数0x0022,即十进制的34,这就代表常量池中有33项常量,索引值范围为1~33。Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始。

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

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

这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 表示一个模块中开放或者导出的包

常量池非常繁琐,17种常量类型各自有着完全独立的数据结构,彼此之间没有什么共性和联系。

我们直接看一下常量池中的17种数据类型的结构总表:

image.png

image.png

image.png

4、访问标志

在常量池结束之后,紧接着的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个,没有使用到的标志位要求一 律为零。

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

这三者为什么放在一起呢?因为这三者用来确定类的继承关系。

类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了  java.lang.Object外,所有Java类的父类索引都不为0。

接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements关键字(后的接口顺序从左到右排列在接口索引集合中。

6、字段表集合

接口索引结束后,接着是字段表(field_info),它用于描述接口或者类中声明的变量——这里的字段(Field)只包括类级变量以及实例级变量,不包括在方法内部声明的局部变量。

描述的主要信息包括:

①、字段的作用域(public,protected,private修饰)

②、是类级变量还是实例级变量(static修饰)

③、是否可变(final修饰)

④、并发可见性(volatile修饰,是否强制从主从读写)

⑤、是否可序列化(transient修饰)

⑥、字段数据类型(8种基本数据类型,对象,数组等引用类型)

⑦、字段名称

字段表的结构如下:

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

access_flags是该字段的的访问标志,它和类中的访问标志很类似,用以描述该字段的权限类型:private、protected、public;并发可见性:volatile;可变性:final;

访问标志详情如下图所示:

image.png

由于Java语法规则的约束,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三个标志最多只能选择其一,ACC_FINAL、ACC_VOLATILE不能同时选择。接口之中的字段必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志。

7、方法表集合

方法表的结构如同字段表一样,依次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项,如表所示:

image.png

有区别的部分只有方法访问标志access_flag, 因为volatile关键字和transient关键字不能修饰方法。

方法表标志位及其取值如下:

image.png

8、属性表集合

接下来终于到了最后一项:属性表集合。

前面提到的Class文件、字段表、方法表都可以携带自己的属性表集合,就是引用的这里。

属性表集合中的属性如下所示:

image.png

image.png

与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制宽松一些,不再要求各个属性表具有严格顺序,并且《Java虚拟机规范》允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。


目录
相关文章
|
1月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
22 3
|
2月前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
65 10
|
2月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
37 3
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
60 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
4月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
34 3
|
4月前
|
存储 安全 Java
JVM内存结构
这篇文章详细介绍了Java虚拟机(JVM)的内存结构,包括类的加载过程、类加载器的双亲委派机制、沙箱安全机制、程序计数器、Java栈、Java堆、本地方法和本地方法栈等关键组件及其作用。
JVM内存结构
|
4月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
246 0
|
5月前
|
Java
jmap 查看jvm内存大小并进行dump文件内存分析
jmap 查看jvm内存大小并进行dump文件内存分析
127 3
|
4月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
5月前
|
存储 运维 Java
Java面试题:JVM的内存结构有哪些主要部分?请简述每个部分的作用
Java面试题:JVM的内存结构有哪些主要部分?请简述每个部分的作用
66 9