[一]class 文件浅析 .class文件格式详解 字段方法属性常量池字段 class文件属性表 数据类型 数据结构
程序员文章站
2022-05-25 14:44:28
class文件是java 平台无关性的基础,也是语言无关性的战略实现之一.想要了解jvm的运行机制,class文件的基础结构是必须要了解熟悉的一个部分,本文从整体逻辑概念思路上对class文件的格式进行了详尽的解读 ......
前言概述
本文旨在讲解class文件的整体结构信息,阅读本文后应该可以完整的了解class文件的格式以及各个部分的逻辑组成含义
class文件包含了java虚拟机指令集 和 符号表 以及若干其他辅助信息.
class文件是一组以8位字节为基础单位的二进制字节流
各个数据项按照顺序紧凑的排列在class文件中,中间没有任何分隔符号
class文件采用类似 c结构体的格式存储数据
数据类型只有两种
无符号数 和 类c结构体的 表 表是由无符号数或者其他的表构成的
整个class文件就是一张表
无论无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器用于指示接下来的数据个数,然后是若干个连续的数据项
class文件主要内容为: 类本身的信息 字段 方法 常量池 以及方法中的code属性 再就是一些相关的辅助信息
类本身的信息类本身有一些必备的描述信息,比如类名 访问修饰符 继承关系等
字段用于描述接口或者类中声明的变量
字段包括类变量以及实例变量,不包括局部变量 他有访问标志 名称 描述符信息
方法用于描述方法表信息 类似字段 也有访问标志 名称 描述符信息
常量池可以理解为class文件的资源仓库,所以他是与其他项目关联最多的数据类型
主要是两大类: 字面量 以及符号引用
字面量接近java语言层面的常量概念 比如文本字符串 声明为final常量的值
符号引用包括:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
虚拟机加载class文件的时候动态链接,所以class文件中不会保存方法的最终内存布局, 还需要转换
虚拟机运行时从常量池中获得对应的符号引用,然后在创建或者运行时解析翻译到具体的内存地址中
code属性存放的java方法的内容,位于方法method_info 内
存放的是java方法经过编译器编译成的字节码指令 说白了存放的就是代码的编译后形式
概述:
class文件作为jvm的"机器语言" 主要包括两部分的信息,基础信息以及附加信息
基础信息为源代码中呈现的信息
类自身信息/字段/方法用于描述源代码中的类/字段/方法
常量池中保存了资源信息,比如字段的名字 方法的描述符等
方法中的code属性保存了方法中的代码的执行逻辑
额外信息为虚拟机执行过程中或者字节码指令执行所需要的信息
为了保证虚拟机能够正确的加载class文件
另外虚拟机加载类还需要做一些额外的工作比如校验信息等
字节码指令的执行可能还需要一些额外的信息
这些额外的信息通常也是保存在常量池中或者以属性的形式出现
|
所以想要理解class文件的内容,就需要从这两个方面出发
基础信息以及额外附加信息
基础信息是对于源代码的映射
给出任意一段java代码,源代码中到底有哪些信息呢?
比如
类的全限定名 类的属性信息 访问权限等
字段的名称 类型信息 访问权限等
方法的名称 方法签名 返回值类型 访问权限等
类或者方法或者字段 有没有注解?
类的父类是什么?
类继承了哪些接口? 等等等等
其实换句话说你所有使用依赖的功能,都需要有底层的数据来支撑
额外的附加信息主要涉及到字节码指令以及虚拟机加载相关原理,额外的附加信息是附属于基本信息的,他们渗透在基本信息里面
所以下面的说明中,我们以基础信息为纲要,涉及到的附加信息会说明或者从功能上看出来是作为附加信息
class文件的数据格式了解
class文件包含了虚拟机所需要知道的,关于类或者接口的所有信息
结构体
他的基本数据类型为无符号数,以及表 表 是 数据组织结构类似于c语言中的结构体的的一种形式
为了更好地理解这种形式的逻辑,不了解c语言的,可以稍微了解一点结构体的形式,更有利于理解class文件的数据形式
struct 结构体名
{
类型名1 成员名1;
类型名2 成员名2;
.....
类型名n 成员名n;
};
|
比如
struct student { char name[10]; char sex; int age; float score; };
他的每个元素的数据类型都可以不相同
而且每个字段本身也可以是指向另外一个数据项的地址
也类似与数据库中的关联字段id,这个id在另一个表中有代表一整条的记录
比如学生表有addressid字段,用于关联地址信息
地址是一条完整的记录,其中可能包括 国家地区 省市 乡镇等等字段值
class文件中的数据类型
每一个class文件都是由字节流组成
一个字节8位
所有的16位 32位 和 64位数据长度都可以通过构造成2个 4个或者8个字节来表示
多字节的数据将会大端排序(高位字节在地址最低位 )
ps: 所谓大小端 |
对于字节的描述,java虚拟机规范中使用u1 u2 u4 u8来分别表示1,2,4和8 个字节的无符号数
基本数据类型为u1,u2,u4,u8
复杂的数据类型由类似结构体的形式来组织无符号数或者是类结构体的形式 可以称之为 表 也就是说表 里面可以有表
比如常量池表中的数据结构为
cp_info{ u1 tag; u1 info[ ] }
所以说
class文件的形式是一张巨大的表,是一个二进制字节流
只有两种数据表示形式 无符号数 以及 表(结构体 复合的数据结构)
各个数据项严格的按照顺序存放,之间没有填充或者对齐,这也是为何编译后代码如此紧凑的原因之一
基本数据类型为: u1 u2 u4 u8
|
class文件的数据组织格式解读
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]; }
class文件是一张表
这张表里面 记录了class文件本身的相关信息 比如magic 以及版本号
class文件中类索引 this_class 父类索引 super_class 以及接口集合 interfaces 三个数据项确定了类的继承关系
同时又记录了字段 方法 以及属性信息
还是以之前的例子为例
源代码
public class helloworld { private int x; private string y; public void fun() { } public static void main(string[] args) { system.out.println("hello world"); } }
使用javap解析后的数据
使用winhex打开.class 文件 的部分结果
我们对照着class文件的结构进行解析
注意:
上图中一列是一个字节 8位 也就是相当于u1 4位表示一个十六进制数
所以一列表示两个十六进制数
第一项 u4 magic
复合class文件魔数设置0xcafebabe
|
第二项 u2 minor_version 第三项 u2 major_version 所以说 主版本号为 52(34是十六进制) 次版本号为0 与javap解析后的数据吻合 |
第四项 u2 constant_pool_count 十六机制27 十进制39 可以看到javap解析后的constant pool:中总共有从#1 到 #38 常量池计数器constant_pool_count的值等于常量表中的成员数加1 常量池标的索引值只有大于0 且小于constant_pool_count时才有效 所以此处解析也是对的 |
第五项 cp_info constant_pool[constant_pool_count-1] 他是常量池 常量池表中的所有项目的格式为 cp_info{ u1 tag; u1 info[];
具体形式由tag的值决定 因为常量池计数为39 常量池中个数为[constant_pool_count-1]所以是38 也就是接下来会有38个 {u1 和 多个u1 } 这种形式的数据组合 第一个tag tag = 7 表示 constnt_class 结构为 constant_class_info{ u1 tag; u2 name_index;
name_index 的值是常量池中的一个索引 他指向的是2号索引 也就是 在接下来的一个u1 是下一个常量池数据项的tag tag 为1 表示constant_utf8 他的结构为 constant_utf8_info{ u1 tag; u2 length; u1 bytes[length];
表示长度为10 在接下来是数组的起始地址,长度为10 那么就是10个u1 我们翻一下ascii码表,对照下十六进制与字符 48 h 65 e 6c l 6c l 6f o 57 w 6f o 72 r 6c l 64 d 也就是helloworld 这其实就是我们的类名 |
不在继续一一比对,你可以使用javap 进行查看
这是官方提供的class文件的解析工具
总结:
class文件的存储形式就是一个二进制字节流
使用类似结构体的形式
将源代码映射的基础信息以及运行时必要的辅助信息,而这些基础信息也都已经被分割为最小的数据单位
进行合理紧凑的组织起来
class文件详解
classfile文件格式
classfile { u4 magic;//唯一作用是确定这个文件是否为一个能被虚拟机所接受的class文件。魔数值固定为0xcafebabe,不会改变 u2 minor_version;//唯一作用是确定这个文件是否为一个能被虚拟机所接受的class文件。魔数值固定为0xcafebabe,不会改变 u2 major_version;//主版本号 u2 constant_pool_count;//常量池计数 值等于常量池表中的成员个数加1 cp_info constant_pool[constant_pool_count-1];//常量池 1~ constant_pool_count-1 为索引 u2 access_flags;//访问标志以及类型信息 u2 this_class;//当前类索引 指向常量池中一个constant_class_info u2 super_class;//父类索引 0 或者指向常量池中一个constant_class_info u2 interfaces_count;//直接超接口数量 u2 interfaces[interfaces_count];//接口表 u2 fields_count;//字段个数 static类变量或者非sttic的实例变量 不包括继承的 field_info fields[fields_count];//字段表 u2 methods_count;//方法个数 所有方法 但不包括继承而来的 method_info methods[methods_count];//方法表 u2 attributes_count;//属性个数 attribute_info attributes[attributes_count];/属性表 }
从class文件的数据结构上来看,主要有下面几部分信息内容
class文件本身的信息 magic minor_version minor_version
类本身的信息 access_flags this_class super_class interfaces_count interfaces[interfaces_count]
常量信息 constant_pool_count constant_pool[constant_pool_count-1]
字段 fields_count fields[fields_count]
方法 methods_count methods[methods_count]
属性 attributes_count attributes[attributes_count]
各种名称的内部表示形式
在进入更加详细的说明之前,有一部分内容必须提前说一下
那就是一些内部名称数据的表示形式
就好像编码一样,亦或者理解成书写格式与规范,比如我们会把姓写在名的前面
对于class文件中描述的一些信息,我们有固定的格式的信息描述符号
比如下面会提到的我们用 d表示是double类型
主要涉及两部分内容 名称 和描述符
名称描述
类和接口的二进制名称
class文件中的类和接口的二进制名称 是通过全限定名称来进行表示的
称之为二进制名称
注意,全限定名的分割形式不再是 英文句号 . 而是 / 比如 java/lang/system
非限定名
方法名 字段名 局部变量名以及形式参数的名都采用非限定名的形式
描述符
分为字段描述符/方法描述符
字段描述符
上面截图自the java virtual machine specification, java se 8 edition
他表示字段描述符使用fieldtype来进行表述
fieldtype有包括基本类型/对象类型/数组类型
形式是
fieldtype
|
b | byte | [基本类型] 有符号的字节数组 |
c | char | [基本类型] 基本多语种平面中的unicode代码点 utf-16 |
d | double | [基本类型] 双精度浮点数 |
f | float | [基本类型] 单精度浮点数 |
i | int | [基本类型] 整型数 |
j | long | [基本类型] 长整数 |
s | short | [基本类型] 有符号短整数 |
z | boolean | [基本类型] 布尔值true/false |
l classname; | l classname; | [对象类型] classname类的实例 |
[ | reference | [数组类型] 一维数组 |
比如int 为i object 为 l java/lang/object; double[][][] d 为 [[[d
方法描述符
上面截图自the java virtual machine specification, java se 8 edition
他表示一个方法描述符由一个参数描述符parameterdescriptor 和一个返回类型描述符returndescriptor组成
参数类型描述符是: fieldtype 类型
返回类型描述符是: fieldtype类型或者void类型voiddescriptor
void类型描述符 使用的是v来进行表示
形式是
( {parameterdescriptor} ) returndescriptor
注意: {} 不是一部分,是想表达和数组似的,也可能是多个
|
比如
object m(int i, double d, thread t) {...}
他的描述符为:
(idljava/lang/thread;)ljava/lang/object;
class文件详解之类本身信息
类本身的信息
access_flags
this_class super_class interfaces_count interfaces[interfaces_count]
名称 | 值 | |
acc_public | 0x0001 | 声明为public 包外访问 |
acc_final | 0x0010 | final 不允许子类 |
acc_super | 0x0020 | 调用invokespecial 需要处理父类 |
acc_interface | 0x0200 | 这是一个接口 |
acc_abstract | 0x0400 | abstract 抽象的不能被实例化 |
acc_synthetic | 0x1000 | class文件并非由java源代码生成 |
acc_annotation | 0x2000 | 注解 |
acc_enum | 0x4000 | 枚举 |
access_flag 字段为类的访问权限标志以及类型值
this_class super_class interfaces[interfaces_count] 构成了类的继承关系 指向的都是常量池中的constant_class_info
对于super_class来说,可能为0 因为object没有父类,其他所有都需要有对应的值存在
class文件详解之常量池
主要分为两类 字面量 符号引用
字面量类似java语言层面的含义 文本字符串 声明为final 的常量值
符号引用包括:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
常量池包含了class文件结构及其子结构中引用的所有的,字符串常量,类或者接口名,字段名,以及其他常量
|
class文件由无符号数和表结构两种类型组成
关于名称的书写规范格式上面已经进行了描述
但是怎么表述这些名称的"字符串" 形式呢? 比如classname = "helloworld" 怎么保存helloworld?
另外一些基本的数据类型的数据在class文件中又将是如何存放呢?比如 int类型的x=5 这个5又怎么保存?
字符串以及不同数值类型也都不就是一个u1 所以也需要组织形式也就是数据结构 也就是表
常量池中的表结构的类型可以分为三种类型
基本数据类型,比如 int long的描述形式,
虽然class文件是二进制字节流,最小为u1 但是这些基本数据类型在逻辑意义上来说,才是最小的描述单位
|
用于表述, 用于描述各个部分包含的逻辑内容的表 "结构体" 复合形式的数据类型结构
|
中间的映射结构表 相当于数据库中的中间关系表
|
另外所有的常量池中的数据都是cp_info形式,所有的常量池中的数据都完全遵循这个格式
cp_info{ u1 tag; u1 info[]; }
只不过info[]具体的格式,由tag来进行决定
也就是说不同的tag 那一块区域的大小以及表示的含义自然是不同的
其实tag 跟我们平时写代码时候的类型是一个道理 就是接下来这块区域的内容的类型标记
tag值如下:
也就是总共有下面这些类型的常量池信息
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 |
常量池中的基础数据类型部分
我们先说下常量池中的封装好的数据类型部分
字符串常量 |
constant_utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
|
tag是constant_utf8 1 字符串采用改进过的utf-8编码表示 接下来是编码后的字符串占用字节数以及字符串 class文件中的方法字段名称都是此类型 |
发表评论