JVM学习笔记(三):类文件结构
代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。
实现语言无关性的基础是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内的任何语言绑定,只与"Class文件"这种特定的二进制文件所关联,Class文件中包含了Java虚拟机指令集合符号表以及若干其它辅助信息。Java虚拟机作为一个通用的、机器无关的执行平台,任何其他语言都可以将其作为语言的产品交付媒介。
Java虚拟机提供的语言无关性:
Class文件是一组以8位字节为基础单位的二进制流,各项数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,如果是超过8位字节以上空间的数据项,则会按照高位在前的方式分割成若干个8位字节进行存储。
Class文件中有两种数据类型:无符号数和表。
无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数;可用来描述数字,索引引用,数量值或者按照UTF-8编码构成的字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以”_info”结尾。表用于描述由层次关系的复合结构的数据,整个Class文件本质上就是一张表。
Class文件格式:
图来自:Java类文件结构详解
更具体而言:
下面以一个简单的Java代码为例进行说明:
package chapter02; public class ClassTest { private int m; public int inc(){
return m + 1;
} }
生成的Class文件如下所示(可用十六进制编辑器WinHex打开.class文件,WinHexagon也可在这里下载):
下面具体来分析Class文件中各项数据的含义
1. 魔数
每个Class文件的头4个字节称为魔数,用于验证该文件是否为一个能够被虚拟机接受的Class文件。Java的Class文件魔数固定为:0xCAFEBABE
2. Class文件的版本
魔数后4个字节,第5个和第6个字节是次版本号,第7个和第8个字节是主版本号。这里次版本号为:0x0000,主版本号为0x0034,转换为十进制为:52,即使用的JDK版本为1.8
3. 常量池
(1). 常量池容量计数值
计数从1开始,目的是满足某些常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池”的含义。
这里常量池容量为0x0016,即十进制的22,代表常量池中有21项常量,索引值范围为1~21。
(2). 常量池
常量池中主要存放两大类常量:字面量和符号引用。
字面量接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。
符号引用属于编译原理方面概念,主要包含以下三类常量: 1) 类和接口的权限定名;2) 字段的名称和描述符; 3) 方法的名称和描述符
常量池中每一项常量都是一个表,这些表都具有如下通用格式:
表开始的第一位是一个标志位(tag),后面info[]项的内容tag的类型所决定。
下面进行具体分析:
常量1:
07 //标志位,表示这个常量属于CONSTANT_Class_info类型。CONSTANT_Class_info类型表示这个常量是一个类或接口的符号引用
00 02 //索引值,指向常量池中的第二项常量。
常量2(即常量1指向的第二项常量):
01 //标志位,表示这个常量属于CONSTANT_Utf8_info类型。CONSTANT_Utf8_info类型表示这个常量是一个UTF-8编码的字符串
00 13 //length值,表示这个UTF-8编码的字符串占用的字节数,它后面紧跟着长度为“length值”的连续数据是一个使用UTF-8缩略编码表示的字符串。0x0013的十进制为19,也就是长19字节
63 68 61 70 74 65 72 30 32 2F 43 6C 61 73 54 65 73 74//长度为19个字节,表示chapter02/ClassTest
CONSTANT_Class_info类型的表和CONSTANT_Utf8_info类型的表的具体结构:
到此为止我们分析了ClassTest.class常量池中21个常量中的两个,其余19个常量也可通过类似的方法计算出来。同时,我们也可以通过javap命令行查看:
可以看出,图中的第1、2项常量与我们手工计算的结果一致。(javap命令:cd到.class的目录下,执行javap命令)
4. 访问标志
在常量池结束之后,紧跟着的两个字节代表访问标志,用于识别一些类或者接口层次的访问信息。
00 21 //表示该类是一个public类型
5. 类索引、父类索引、接口索引集合
类索引和父类索引各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。
对于接口索引集合,入口第一项为接口计数器,表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面的接口索引表不再占用任何字节。
00 01 //类索引,表示chapter02/ClassTest
00 03 //父类索引,表示java/lang/Object。(Java中所有的类都有父类,除了java.lang.Object)
00 00 //接口索引集合大小,因为该类没有实现任何接口,所以值为0
查询javap命令计算出来的常量池,找出对应的类和父类的常量:
6. 字段表集合
字段表用于描述接口或类中声明的变量。字段包括类级别变量以及实例变量,但不包括在方法内部声明的局部变量。包含的信息有:字段的作用域(public 、private、protected修饰符)、类变量还是实例变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读取)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合用标志位来表示。而字段叫什么名字,字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
00 01 //容量计数器fields_count,说明这个类只有一个字段表数据
00 02 //字段修饰符access_flags,表示private修饰符为真,即该字段修饰符为private
00 05 //字段名称name_index,从javap命令行计算出的常量池表中,可知第5项常量是一个CONSTANT_Utf8_info类型的字符串,其值为"m"
00 06 //字段描述符descriptor_index,从javap命令行计算出的常量池表中,可知第6项常量是一个CONSTANT_Utf8_info类型的字符串,其值为"I",代表该字段是一个int类型的值
可以看出,修饰符是用标志位表示的,而字段名称(name_index)和字段类型(descriptor_index),是从常量池中读取的。
7. 方法表集合
方法表的结构同字段表一样,依次包含了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。
至此,可知,方法的定义可通过访问标志、名称索引、描述符索引表达清楚[如:public static void testTenuringThreshold()方法];方法里的代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为"Code"的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目。
00 02 //计数器容量,代表集合中有两个方法(分别为编译器添加的实例构造器<init>和源代码中的方法inc())
00 01 //访问标志,表示只有ACC_PUBLIC标志为真,即该方法的修饰符为public
00 07 //名称索引,通过查询常量池表,可得方法名为"<init>"
00 08 //描述符索引值,通过查询常量池表,可得对应常量为"()V"
00 01 //属性表计数器,表示此方法的属性表集合有一项属性
00 09 //属性名称索引,对应常量为"Code",说明此属性是方法的字节码描述,即Java方法里面的代码
8. 属性表集合
java class文件内部属性信息,和java语言定义的属性没有关系,纯粹就是给java虚拟机用的。
对于每一个属性,它的名称需从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,需需要通过一个长度属性去说明属性值所占用的位数即可。
Code属性:
Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。
00 01 //max_stack,代表操作数栈深度的最大值,这里操作数栈最大深度为1。在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧。
00 01 //max_locals,代表局部变量表所需的存储空间,这里最大的局部变量数为1。
00 00 00 05 //code_length,代表字节码长度,这里方法代码的长度为5。code_length和code用来存储Java源程序编译后生成的字节码指令。
2A B7 00 0A B1 //code,用于存储字节码指令的一系列字节流。
//具体分析2A B7 00 0A B1
2A //查表得0x2A对应的指令为aload_0,这个指令的含义是将第0个Slot中为reference类型的本地变量推送到操作数栈顶
B7 //查表的0xB7对应的指令为invokespecial
00 0A //这是invokespecial的参数,查常量池得0x000A对应的常量为实例构造器"<init>"方法的符合引用
B1 //查表得0xB1对应的指令为return,含义是返回此方法,并且返回值为void。这条指令执行后,当前方法结束。
参考:
JVM(四)类文件结构解析
Java字节码结构解析
《深入理解java虚拟机 JVM高级特性与最佳实践》
JVM学习笔记(三):类文件结构的更多相关文章
- jvm虚拟机笔记<三> 类文件结构与类加载机制
java虚拟机具有语言无关系,它只和“class文件“这种特定的二进制文件格式绑定. 不同语言的编译器将对应的程序编译成字节码文件(*.class),送给jvm执行. class文件本质上就是一张表, ...
- java之jvm学习笔记三(Class文件检验器)
java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...
- JVM学习笔记之class文件结构【七】
一.概念 1.1 无符号数: 以 u1.u2.u3.u4.u8 代表 1 个字节,2 个字节.4 个字节.8 个字节的无符号数.无符号数可以描述数字,索引引用.数量值和按照 UTF-8 编码构成的字符 ...
- java jvm学习笔记三(class文件检验器)
欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验 ...
- JVM学习笔记三:垃圾收集器与内存分配策略
内存回收与分配重点关注的是堆内存和方法区内存(程序计数器占用小,虚拟机栈和本地方法栈随线程有相同的生命周期). 一.判断对象是否存活? 1. 引用计数算法 优势:实现简单,效率高. 致命缺陷:无法解决 ...
- JVM学习笔记三:垃圾收集器及内存管理策略
垃圾收集器 上文说到了垃圾收集算法,这次我们聊一下HotSpot的具体垃圾收集器的实现,以JDK1.7为例,其包含的可选垃圾收集器如下图: 不同收集器之间的连线,代表它们可以搭配使用,收集器所属的区域 ...
- JVM学习笔记-第六章-类文件结构
JVM学习笔记-第六章-类文件结构 6.3 Class类文件的结构 本章中,笔者只是通俗地将任意一个有效的类或接口锁应当满足的格式称为"Class文件格式",实际上它完全不需要以磁 ...
- JVM学习笔记-第三章-垃圾收集器与内存分配策略
JVM学习笔记-第三章-垃圾收集器与内存分配策略 tips:对于3.4之前的章节可见博客:https://blog.csdn.net/sanhewuyang/article/details/95380 ...
- JVM学习04:类的文件结构
JVM学习04:类的文件结构 写在前面:本系列分享主要参考资料是 周志明老师的<深入理解Java虚拟机>第二版. 类的文件结构知识要点Xmind梳理
随机推荐
- jqgrid 单击行启用行编辑,切换行保存原编辑行
为了加速表格互动编辑,我们往往希望通过选中行就触发了行编辑,完成行编辑后,再选中另一个行做编辑,同时上一个编辑行被自动保存,直至完成需要的编辑内容. 页面效果可能如下: 1)设置需要编辑的列 edit ...
- 板载CAN的树莓派扩展板Strato Pi CAN
板载CAN的树莓派扩展板Strato Pi CAN Sfera Labs推出了最新的树莓派扩展组件“灵云派”,其中包括CAN总线,电气隔离的RS-485,RTC和9-65V电源. 位于意大利米兰的 ...
- [Jxoi2012]奇怪的道路 BZOJ3195 状压DP
分析: k很小,可以状压. f[S][i]表示状态S表示在i之前k+1个中点的边数奇偶情况 之后转移的时候,S的最后一位不能为1 附上代码: #include <cstdio> #incl ...
- 如何查看哪个进程,使用了哪个CPU
某些时候,我们需要知道,在Unix/Linux 环境中,CPU究竟消耗在了哪些进程上面. 如下是最简单的方法: ps -elF
- Wannafly挑战赛26-F-msc的棋盘[最小割转化dp]
题意 一个大小为 \(n*m\) 的棋盘,知道每一列放了多少棋子,求有多少摆放方案满足要求. \(n,m\leq 50\) . 分析 如果是求是否有方案的话可以考虑网络流,行列连边,列容量为 \(b_ ...
- Elasticsearch Java Rest Client API 整理总结 (一)——Document API
目录 引言 概述 High REST Client 起步 兼容性 Java Doc 地址 Maven 配置 依赖 初始化 文档 API Index API GET API Exists API Del ...
- C# Language Specification 5.0 (翻译)第六章 转换
转换使表达式可以当做一个明确的类型来加以处理.转换使得所给定类型的表达式以不同类型来处理,或使得没有某个类型的表达式获得该类型.转换可以是显式或隐式的,而这决定了是否需要显式地强制转换.比方说,从类型 ...
- 全面掌握IO(输入/输出流)
File类: 程序中操作文件和目录都可以使用File类来完成即不管是文件还是目录都是使用File类来操作的,File能新建,删除,重命名文件和目录,但File不能访问文件内容本身,如果需要访问文件本身 ...
- 1.0.0 Unity零基础入门——打砖块
1)设置好相应场景 2)创建脚本挂载到相应物体上并编写 2.代码 //Shoot - - 控制小球生成与射击 using System.Collections; using System.Collec ...
- ELK日志方案--使用Filebeat收集日志并输出到Kafka
1,Filebeat简介 Filebeat是一个使用Go语言实现的轻量型日志采集器.在微服务体系中他与微服务部署在一起收集微服务产生的日志并推送到ELK. 在我们的架构设计中Kafka负责微服务和EL ...