每一个Class都对应着唯一的一个类或借口的定义信息。这里,我们称为"Class文件格式"只是通俗的将任意一个符合有效的类或借口的格式这么称呼,但是它并不一定是以磁盘文件的形式存在。

每个Class文件都是由8字节为单位的字节流组成,所有的16位、32位和64位长度的数据将被构造成 2个、4个和8个8字节单位来表示。

ClassFile结构

每一个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];}

其中u1、u2、u4分别代表1、2、4个字节无符号数。

magic:

魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。

minor_version、major_version:

分别为Class文件的副版本和主版本。它们共同构成了Class文件的格式版本号。不同版本的虚拟机实现支持的Class文件版本号也相应不同,高版本号的虚拟机可以支持低版本的Class文件,反之则不成立。

constant_pool_count:

常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。

constant_pool[]:

常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1。

access_flags:

访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags的取值范围和相应含义见下表:

this_class:

类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。

super_class:

父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当然,如果某个类super_class的值是0,那么它必定是java.lang.Object类,因为只有它是没有父类的。

interfaces_count:

接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。

interfaces[]:

接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量。

fields_count:

字段计数器,fields_count的值表示当前Class文件fields[]数组的成员个数。

fields[]:

字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。

methods_count:

方法计数器,methods_count的值表示当前Class文件methods[]数组的成员个数。

methods[]:

方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。

attributes_count:

属性计数器,attributes_count的值表示当前Class文件attributes表的成员个数。

attributes[]:

属性表,attributes表的每个项的值必须是attribute_info结构。

下面举个简单的例子来说明一下ClassFile的结构:

public class HelloWorld{String str = "";public String getStr(){return str;}public void setStr(String str){this.str = str;} }

通过javap工具我们能看到这个简单的类的结构,如下:

我们可以看到一些信息包括主副版本号、常量池、ACC_FLAGS等,再来打开Class文件看一下:

根据前面所述的ClassFile结构,我们来分析下:

可以看到前4个字节为魔数,也就是0xCAFEBABE,这里都是十六进制。

魔数后2个字节为副版本号,这里副版本号是0.

再后2个字节是主版本号0x0033,转为十进制,主版本号是51,和Javap工具所看到的一样,这里我用的JDK版本是1.7。

这两个字节是常量池计数器,常量池的数量为0x0017,转为十进制是23,也就是说常量池的索引为1~22,这与Javap所看到的也相符。   常量池计数器后面就是常量池的内容,我们根据javap所看到的信息找到最后一个常量池项java/lang/Object,在字节码中找到对应的地方:

常量池后面两个字节是访问标志access_flags:

值为0x0021,在javap中我们看到这个类的标志是

其中ACC_PUBLIC的值为0x0001,ACC_SUPER的值为0x0020,与字节码是相匹配的。   至于ClassFile的其他结构,包括this_class、super_class、接口计数器、接口等等都可以通过同样的方法进行分析,这里就不再多说了。

下面将详细的介绍一下ClassFile结构中的中的各个部分。

常量池

所有的常量池项都具有如下通用格式:

cp_info {   u1 tag;   u1 info[]; }

以1个字节的tag开头,后面info[]项的内容tag由的类型所决定。tag有效的类型和对应的取值如下表:

下面我们来介绍下不同类型的tag所对应的结构和规则:

CONSTANT_Class_info结构:

CONSTANT_Class_info结构用于表示类或接口,格式如下:

CONSTANT_Class_info{u1 tag;u2 name_index;}

CONSTANT_Class_info结构的tag项的值为CONSTANT_Class(7)。name_index项的值,必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,代表一个有效的类或接口二进制名称的内部形式。

CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info结构:

字段,方法和接口方法由类似的结构表示:

CONSTANT_Fieldref_info{u1 tag;u2 class_index;u2 name_and_type_index;}CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }

CONSTANT_Fieldref_info结构的tag项的值为CONSTANT_Fieldref(9)。

CONSTANT_Methodref_info结构的tag项的值为CONSTANT_Methodref(10)。 CONSTANT_InterfaceMethodref_info结构的tag项的值为CONSTANT_InterfaceMethodref(11)

class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。 name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。   CONSTANT_String_info结构:

CONSTANT_String_info用于表示java.lang.String类型的常量对象,格式如下:

CONSTANT_String_info{u1 tag;u2 string_index;}

CONSTANT_String_info结构的tag项的值为CONSTANT_String(8)。string_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一组Unicode码点序列,这组Unicode码点序列最终会被初始化为一个String对象。

CONSTANT_Integer_info和CONSTANT_Float_info结构:

CONSTANT_Intrger_info和CONSTANT_Float_info结构表示4字节(int和float)的数值常量:

CONSTANT_Integer_info{u1 tag;u4 bytes;}CONSTANT_Float_info{u1 tag;u4 bytes;}

CONSTANT_Integer_info结构的bytes项表示int常量的值,按照Big-Endian的顺序存储。 CONSTANT_Float_info结构的bytes项按照IEEE 754单精度浮点格式。表示float常量的值,按照Big-Endian的顺序存储。

CONSTANT_Long_info和CONSTANT_Double_info结构:

CONSTANT_Long_info和CONSTANT_Double_info结构表示8字节(long和double)的数值常量:

CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; } CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; }

在Class文件的常量池中,所有的8字节的常量都占两个表成员(项)的空间。如果一个CONSTANT_Long_info或CONSTANT_Double_info结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2,此时常量池中索引为n+1的项有效但必须被认为不可用。

CONSTANT_Long_info结构的tag项的值是CONSTANT_Long(5)。 CONSTANT_Double_info结构的tag项的值是CONSTANT_Double(6)。

CONSTANT_Long_info结构中的无符号的high_bytes和low_bytes项用于共同表示long型常量,构造形式为((long) high_bytes << 32) + low_bytes,high_bytes和low_bytes都按照Big-Endian顺序存储。 CONSTANT_Double_info结构中的high_bytes和low_bytes共同按照IEEE 754双精度浮点格式表示double常量的值。high_bytes和low_bytes都按照Big-Endian顺序存储。

CONSTANT_NameAndType_info结构:

CONSTANT_NameAndType_info结构用于表示字段或方法,但是和前面介绍的三个表示字段方法的结构不同,CONSTANT_NameAndType_info结构没有标识出它所属的类或接口,格式如下:

CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }

CONSTANT_NameAndType_info结构的tag项的值为CONSTANT_NameAndType(12)。

name_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名。   descriptor_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,这个结构表示一个有效的字段描述符或方法描述符。

CONSTANT_Utf8_info结构:

CONSTANT_Utf8_info结构用于表示字符串常量的值:

CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }

CONSTANT_Utf8_info结构的tag项的值为CONSTANT_Utf8(1)。length项的值指明了bytes[]数组的长度,bytes[]是表示字符串值的byte数组。

CONSTANT_MethodHandle_info结构:

CONSTANT_MethodHandle_info结构用于表示方法句柄,结构如下:

CONSTANT_MethodHandle_info{u1 tag;u1 reference_kind;u2 reference_index;}

CONSTANT_MethodHandle_info结构的tag项的值为CONSTANT_MethodHandle(15)。reference_kind项的值必须在1至9之间(包括1和9),它决定了方法句柄的类型。

reference_index项的值必须是对常量池的有效索引,索引项和reference_kind的对应关系如下:

CONSTANT_MethodType_info结构:

CONSTANT_MethodType_info结构用于表示方法类型:

CONSTANT_MethodType_info { u1 tag; u2 descriptor_index; }

CONSTANT_MethodType_info结构的tag项的值为CONSTANT_MethodType(16)。descriptor_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符。

CONSTANT_InvokeDynamic_info结构:

CONSTANT_InvokeDynamic_info用于表示invokedynamic指令所使用到的引导方法、引导方法使用到动态调用名称、参数和请求返回类型、以及可以选择性的附加被称为静态参数的常量序列。

CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }

CONSTANT_InvokeDynamic_info结构的tag项的值为CONSTANT_InvokeDynamic(18)。bootstrap_method_attr_index项的值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引。ame_and_type_index项的值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符。

下面我们还是使用上面ClassFile的例子来简单看下常量池:

通过javap我们看到常量池中第一项为:

是HelloWorld的初始化方法,再来看一下字节码:

0A是第一个常量池项的tag,转为十进制是10,查找上面的常量类型表的确是CONSTANT_Methodref类型常量。

根据CONSTANT_Methodref_info的结构,tag后2个字节为class_index,为常量池的某个可用索引,索引项必须为CONSTANT_Class_info结构:

0x0005,转为十进制是5。索引位置5的常量为:

可以看到是Object类,Java中所有的类都是Object类的子类。HelloWorld类没有显示的定义构造方法,会自动调用父类Object的无参构造方法。

继续看CONSTANT_Methodref_info的结构的第三个属性name_and_type_index,为常量池的某个可用索引,索引项必须为CONSTANT_NameAndType_info结构:

0x0012,转为十进制是18。索引位置18的常量为:

包括前面的索引5的CONSTANT_Class_info结构和这里的CONSTANT_NameAndType_info结构我们都可以继续追踪下去,这里我只是做简单分析就不再往下了。

用同样的方法可以分析常量池里每一个常量。

字段

每个字段(Field)都由field_info结构所定义,在同一个Class文件中,不会有两个字段同时具有相同的字段名和描述符。

field_info结构格式如下:

field_info{ u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }

access_flags项的值是用于定义字段被访问权限和基础属性的掩码标志。取值范围如下表:

name_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的非全限定名。

descriptor_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的描述符。

attributes_count的项的值表示当前字段的附加属性的数量。

attributes表的每一个成员的值必须是attribute结构,一个字段可以有任意个关联属性。

方法

所有方法(Method),包括实例初始化方法和类初始化方法在内,都由method_info结构所定义。在一个Class文件中,不会有两个方法同时具有相同的方法名和描述符。

method_info结构格式如下:

method_info{u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count];}

access_flags项的值是用于定义当前方法的访问权限和基本属性的掩码标志,取值范围如下表:

标记名        值        说明

ACC_PUBLIC     0x0001     public,方法可以从包外访问

ACC_PRIVATE    0x0002     private,方法只能本类中访问

ACC_PROTECTED  0x0004     protected,方法在自身和子类可以访问

ACC_STATIC     0x0008     static,静态方法

ACC_FINAL      0x0010     final,方法不能被重写(覆盖)

ACC_SYNCHRONIZED     0x0020     synchronized,方法由管程同步

ACC_BRIDGE     0x0040     bridge,方法由编译器产生

ACC_VARARGS     0x0080     表示方法带有变长参数

ACC_NATIVE     0x0100     native,方法引用非java语言的本地方法

ACC_ABSTRACT   0x0400     abstract,方法没有具体实现

ACC_STRICT     0x0800     strictfp,方法使用FP-strict浮点格式

ACC_SYNTHETIC   0x1000     方法在源文件中不出现,由编译器产生

name_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构。

descriptor_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的方法的描述符。

attributes_count的项的值表示这个方法的附加属性的数量。attributes表的每一个成员的值必须是attribute结构,一个方法可以有任意个与之相关的属性。

属性:

属性(Attributes)在Class文件格式中的ClassFile结构、field_info 结构,method_info结构和Code_attribute结构都有使用,所有属性的通用格式如下:

attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }

对于任意属性,attribute_name_index必须是对当前Class文件的常量池的有效16位无符号索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示当前属性的名字。attribute_length项的值给出了跟随其后的字节的长度,这个长度不包括attribute_name_index和attribute_name_index项的6个字节。   对于字段、方法、和属性的结构,我们很容易的可以通过javap工具查看到。

关于Class文件的验证可能会放到后面Java虚拟机加载类的过程中学习。

java 反射驻足类型_《Java虚拟机规范》阅读(三):Class文件格式相关推荐

  1. java虚拟机规范阅读(三)异常

    Java虚拟机里面的异常使用Throwable或其子类的实例来表示,抛异常的本质实际上是程序控制权的一种即时的.非局部(Nonlocal)的转换--从异常抛出的地方转换至处理异常的地方. 绝大多数的异 ...

  2. java反射api研究_深入研究Java 8中的可选类API

    java反射api研究 作为Java程序员,我们所有人都经历了以下情况:我们调用一个方法来获取某个值,然后代替直接对返回值调用某些方法,我们首先必须检查返回值是否不为null,然后在返回值. 这是像G ...

  3. java 反射data类型_java反射机制系列之初识Java Reflection

    烈火建站学院(LieHuo.Net)JAVA文档 JAVA 反射机制是指Java程序可以在执行期载入,探知,使用编译期间完全未知的classes.这句话可能有点难以理解,我们可以通过一个例子来看.在 ...

  4. java invoke 返回类型_解析Java反射 - invoke方法

    最近工作中涉及到获取同程火车票,大概描述为:将本地获取的发出城市,目的城市及出发时间按固定格式封装,调用接口获取可乘坐座席等级最高的火车票,接口返回数据用包含三层类封装的类接受,接受的类总共为四层,倒 ...

  5. java 注解 属性 类型_跟光磊学Java开发-Java注解

    注解概述 注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记以后,java编译器.开发工具或者其他的框架就可以通过反射来获取类以及类的成员上的注解,然后通过作相应的处 ...

  6. java 反射 本类,关于Java反射中基本类型的class有关问题

    关于Java反射中基本类型的class问题 1. 基本类型的class和其对应包装类的class是不同的,所以在获得Method指定参数的时候,需要精确指定参数的类型,即 setInt(int x) ...

  7. java 注解 属性 类型_收藏!你一定要知道的Java8中的注解

    全文共3002字,预计学习时长6分钟 海中有大量的注解! JavaSE 1.5中首次引入了注解.Java注解的目的是允许程序员编写关于其程序的元数据.在OracleDocs中,注解的定义是:" ...

  8. java反射 数组类型_Java反射-数组

    通过反射使用数组有时会比较棘手.特别是需要获得一个特定类型数组的Class对象,如int[]等.本文将讨论如何通过反射创建数组和获得他们的Class对象. 注意:本文在阅读Eyal Lupu的博客&q ...

  9. java 反射机制 视频_【视频笔记】Java反射机制笔记

    Java 语言的反射机制 在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法.对于任意一个对象,可以调用它的任意一个方法. 这种动态获取类的信息以及动态调用对象的方法的功能来自于J ...

最新文章

  1. Keil中的警告的解决
  2. invalid currency could not be saved in AG3
  3. 安装DirectX SDK时出现Error Code:s1023 的解决方案
  4. 三个用户在同一系统中同时对他们的c语言,杭州电子科技大学学生考试卷2013年操作系统试卷(2份,有答案)...
  5. 如何导出已安装的安卓app为apk包
  6. 如何高效学习Oracle
  7. 11月全球搜索引擎市场:百度份额突破30% 增势持续
  8. CentOS7.9 EDA软件,Cadence、Synopsys、Mentor、Ansys、Keysight、Matlab、Vivado和Quartus等工具虚拟机平台
  9. CodeBlock的安装、配置和运行
  10. 高中计算机教师学期论文,高中信息技术论文范文
  11. phpspider案例
  12. NodeMCU文档中文翻译 6 MQTT模块
  13. 基于struts2 拦截器ResultType为chain的Action之间数据传递 ——表单页面打开优化
  14. POM 文件中 licenses 许可证的定义
  15. 机器学习实战(五) kaggle练习赛 泰坦尼克获救预测
  16. access 战地1不加入ea_战地1、泰坦陨落2将正式加入EA Access会免阵容
  17. JS 实战: Drag 点击拖曳效果
  18. 穆利堂[推荐] WxPM信息化整体解决方案-河南郑州房地产工程项目管理系统软件 穆穆-movno1
  19. PM常用语看这篇就够了
  20. 本科毕业论文问卷调查怎么写

热门文章

  1. Compound 治理——提案
  2. [JVM]了断局: “运行时数据区“理论梳理
  3. RabbitMQ-客户端源码之AMQChannel
  4. 线性代数(行列式矩阵向量)
  5. el-checkbox-group 点击box 没反应,无法取消勾选(记一次debug的思路)
  6. 时间工具类: 判断两个时间是否是同一月、是否是同一天
  7. Java之日期与时间
  8. 微信自动回复+图片识别
  9. 使用axis发送xml报文,返回并解析报文实例
  10. QQ登录, 腾讯开放平台和QQ互联的坑