Java Class的文件结构是怎么样的

技术Java Class的文件结构是怎么样的本篇文章为大家展示了Java Class的文件结构是怎么样的,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。今天把之前在Evernote

本文向您展示了Java类的文件结构。内容简洁易懂。它一定会让你闪闪发光。希望通过这篇文章的详细介绍,你能有所收获。

今天重新整理一下Evernote中之前的笔记,发上来供对java类文件结构感兴趣的同学参考。

正在学习Java的朋友应该都知道,Java从一开始就打着平台独立的旗号说“写一次,到处跑”。其实说到独立性,Java平台还有一个独立性,那就是语言独立性。要实现语言独立性,那么Java系统中类的文件结构或字节码就非常重要。事实上,Java从一开始就有两套规范。一个是Java语言规范,一个是Java虚拟机规范。Java语言规范只规定了与Java语言相关的约束和规则,而虚拟机规范真正是从跨平台的角度来设计的。今天,让我们举一个实际的例子来看看Java中对应于一个Class文件的字节码应该是什么样子。在这里,首先,我们将大致解释该类由什么组成,然后使用一个实际的Java类来分析该类的文件结构。

在继续之前,我们需要明确以下几点:

1)类文件由基于8个字节的字节流组成,按严格的顺序排列,字节之间没有间隙。对于大于8字节的数据,将按照Big-Endian的顺序存储数据,即高位字节存储在低位地址,低位字节存储在高位地址。其实这是跨平台类文件的关键。由于PowerPC架构采用Big-Endian的存储顺序,而x86系列处理器采用Little-Endian的存储顺序,为了在每个处理器架构下保持Class文档统一的存储顺序,必须统一虚拟机规范。

2)类文件结构使用类似C语言的结构来存储数据。数据项主要有两种,无符号数字和表格。无符号数字用于表示数字、索引引用和字符串。例如,U1、U2、U4和U8分别表示一个字节、两个字节、四个字节和八个字节的无符号数,而表是由多个无符号数和其他表组成的复合结构。你可能会看到,不清楚无符号数和表格是在上面还是下面,但这并不重要。后面我会用例子来解释。

在阐明了以上两点之后,让我们来看看类文件中严格按顺序排列的字节流具体包含哪些数据:

Java  Class的文件结构是怎么样的

(上图来自Java虚拟机规范Java se 7版)

看上图的时候,需要注意一点。例如,cp_info和cp_info代表常量池。上图中,用constant _ pool[constant_pool_count-1]表示常量池中有constant _ pool _ count-1常量,用数组表示。但是不要把所有的常量池都误认为是相同的常量长度。实际上,这个地方只是为了描述方便才使用数组,但它不像编程语言中的int数组,每个int都有相同的长度。说清楚了,我们再回去看看上图的每一项具体代表了什么。

1)u4幻意为幻数,幻数占用4字节。幻数是做什么的?其实就是说文件类型是Class文件,不是JPG图片或者AVI电影。类文件对应的幻数是0xCAFEBABE。

2)u2 minor_version表示Class文件的次版本号,这个版本号是u2类型的无符号数字表示。

3) u2 major_version表示Class文件的主版本号,主版本号是u2类型的无符号数字表示。main _ version和minor_version主要用于指示当前虚拟机是否接受类文件的当前版本。由Java编译器编译的类文件的不同版本有不同的版本。较高版本的虚拟机支持由较低版本的编译器编译的类文件结构。比如Java SE 6.0对应的虚拟机支持Java SE 5.0编译器编译的Class文件结构,反之亦然。

4) u2常量_pool_count表示常量池的数量。这里,我们需要关注什么是常数池。请不要将其与Jvm内存模型中的运行时常量池混淆。Class文件中的常量池主要存储文字量和符号引用,其中文字量主要包括字符串、最终常量的值或一个属性的初始值等。而符号引用主要存储类和接口的完全限定名、字段和描述符的名称、方法和描述符的名称。这里的名字可能大家都容易理解。至于描述符的概念,除此之外,我们都知道Jvm的内存模型由堆、栈、方法区和程序计数器组成,在方法区有一个叫做Runtime的区域。

量池,运行时常量池中存放的东西其实也就是编译器长生的各种字面量以及符号引用,  只不过运行时常量池具有动态性,它可以在运行的时候向其中增加其它的常量进去,最具代表性的就是String的intern方法。

5)cp_info  表示常量池,这里面就存在了上面说的各种各样的字面量和符号引用。放到常量池的中数据项在The Java Virtual Machine Specification  Java SE 7 Edition 中一共有14个常量,每一种常量都是一个表,并且每种常量都用一个公共的部分tag来表示是哪种类型的常量。

下面分别简单描述一下具体细节等到后面的实例 中我们再细化。

  • CONSTANT_Utf8_info      tag标志位为1,    UTF-8编码的字符串

  • CONSTANT_Integer_info  tag标志位为3, 整形字面量

  • CONSTANT_Float_info     tag标志位为4, 浮点型字面量

  • CONSTANT_Long_info     tag标志位为5, 长整形字面量

  • CONSTANT_Double_info  tag标志位为6, 双精度字面量

  • CONSTANT_Class_info    tag标志位为7, 类或接口的符号引用

  • CONSTANT_String_info    tag标志位为8,字符串类型的字面量

  • CONSTANT_Fieldref_info  tag标志位为9,  字段的符号引用

  • CONSTANT_Methodref_info   tag标志位为10,类中方法的符号引用

  • CONSTANT_InterfaceMethodref_info tag标志位为11,  接口中方法的符号引用

  • CONSTANT_NameAndType_info tag  标志位为12,字段和方法的名称以及类型的符号引用

6) u2 access_flags 表示类或者接口的访问信息,具体如下图所示:

Java Class的文件结构是怎么样的

7)u2 this_class  表示类的常量池索引,指向常量池中CONSTANT_Class_info的常量

8)u2 super_class  表示超类的索引,指向常量池中CONSTANT_Class_info的常量

9)u2 interface_counts 表示接口的数量

10)u2  interface[interface_counts]表示接口表,它里面每一项都指向常量池中CONSTANT_Class_info常量

11)u2 fields_count 表示类的实例变量和类变量的数量

12) field_info  fields[fields_count]表示字段表的信息,其中字段表的结构如下图所示:

Java Class的文件结构是怎么样的

上图中access_flags表示字段的访问表示,比如字段是public,private,protect  等,name_index表示字段名  称,指向常量池中类型是CONSTANT_UTF8_info的常量,descriptor_index表示字段的描述符,它也指向常量池中类型为  CONSTANT_UTF8_info的常量,attributes_count表示字段表中的属性表的数量,而属性表是则是一种用与描述字段,方法以及  类的属性的可扩展的结构,不同版本的Java虚拟机所支持的属性表的数量是不同的。

13) u2 methods_count表示方法表的数量

14)method_info 表示方法表,方法表的具体结构如下图所示:

Java Class的文件结构是怎么样的

其中access_flags表示方法的访问表示,name_index表示名称的索引,descriptor_index表示方法的描述  符,attributes_count以及attribute_info类似字段表中的属性表,只不过字段表和方法表中属性表中的属性是不同的,比如方法  表中就Code属性,表示方法的代码,而字段表中就没有Code属性。其中具体Class中到底有多少种属性,等到Class文件结构中的属性表的时候再 说说。

15) attribute_count表示属性表的数量,说到属性表,我们需要明确以下几点:

  • 属性表存在于Class文件结构的最后,字段表,方法表以及Code属性中,也就是说属性表中也可以存在属性表

  • 属性表的长度是不固定的,不同的属性,属性表的长度是不同的

上面说完了Class文件结构中每一项的构成以后,我们以一个实际的例子来解释以下上面所说的内容。

package com.ejushang.TestClass;   public class TestClass implements Super{   private static final int staticVar = 0;   private int instanceVar=0;   public int instanceMethod(int param){  return param+1;  }   }   interface Super{ }

通过jdk1.6.0_37的javac 编译后的TestClass.java对应的TestClass.class的二进制结构如下图所示:

Java Class的文件结构是怎么样的

下面我们就根据前面所说的Class的文件结构来解析以下上图中字节流。

1)魔数
从Class的文件结构我们知道,刚开始的4个字节是魔数,上图中从地址00000000h-00000003h的内容就是魔数,从上图可知Class的文件的魔数是0xCAFEBABE。

2)主次版本号
接下来的4个字节是主次版本号,有上图可知从00000004h-00000005h对应的是0×0000,因此Class的minor_version  为0×0000,从00000006h-00000007h对应的内容为0×0032,因此Class文件的major_version版本为  0×0032,这正好就是jdk1.6.0不带target参数编译后的Class对应的主次版本。

3)常量池的数量
接下来的2个字节从00000008h-00000009h表示常量池的数量,由上图可以知道其值为0×0018,十进制为24个,但是对于常量池的数量  需要明确一点,常量池的数量是constant_pool_count-1,为什么减一,是因为索引0表示class中的数据项不引用任何常量池中的常 量。

4)常量池
我们上面说了常量池中有不同类型的常量,下面就来看看TestClass.class的第一个常量,我们知道每个常量都有一个u1类型的tag标识来表示  常量的类型,上图中0000000ah处的内容为0x0A,转换成二级制是10,有上面的关于常量类型的描述可知tag为10的常量是  Constant_Methodref_info,而Constant_Methodref_info的结够如下图所示:

Java Class的文件结构是怎么样的

其中class_index指向常量池中类型为CONSTANT_Class_info的常量,从TestClass的二进制文件结构中可以看出  class_index的值为0×0004(地址为0000000bh-0000000ch),也就是说指向第四个常量。

name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0×0013,表示指向常量池中的第19个常量。

接下来又可以通过同样的方法来找到常量池中的所有常量。不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap  -verbose TestClass 即可得到所有常量池中的常量,截图如下:

Java Class的文件结构是怎么样的

从上图我们可以清楚的看到,TestClass中常量池有24个常量,不要忘记了第0个常量,因为第0个常量被用来表示  Class中的数据项不引用任何常量池中的常量。从上面的分析中我们得知TestClass的第一个常量表示方法,其中class_index指向的第四  个常量为java/lang/Object,name_and_type_index指向的第19个常量值为<init>:()V,从这里可  以看出第一个表示方法的常量表示的是java编译器生成的实例构造器方法。通过同样的方法可以分析常量池的其它常量。OK,分析完常量池,我们接下来再分  析下access_flags。
5)u2 access_flags  表示类或者接口方面的访问信息,比如Class表示的是类还是接口,是否为public,static,final等。具体访问标示的含义之前已经说过  了,下面我们就来看看TestClass的访问标示。Class的访问标示是从0000010dh-0000010e,期值为0&times;0021,根据前面说的  各种访问标示的标志位,我们可以知道:0&times;0021=0&times;0001|0&times;0020 也即ACC_PUBLIC 和  ACC_SUPER为真,其中ACC_PUBLIC大家好理解,ACC_SUPER是jdk1.2之后编译的类都会带有的标志。

6)u2 this_class  表示类的索引值,用来表示类的全限定名称,类的索引值如下图所示:

Java Class的文件结构是怎么样的

从上图可以清楚到看到,类索引值为0&times;0003,对应常量池的第三个常量,通过javap的结果,我们知道第三个常量为  CONSTANT_Class_info类型的常量,通过它可以知道类的全限定名称为:com/ejushang/TestClass /TestClass

7)u2 super_class  表示当前类的父类的索引值,索引值所指向的常量池中类型为CONSTANT_Class_info的常量,父类的索引值如下图所示,其值为0&times;0004,  查看常量池的第四个常量,可知TestClass的父类的全限定名称为:java/lang/Object

Java Class的文件结构是怎么样的

8)interfaces_count和   interfaces[interfaces_count]表示接口数量以及具体的每一个接口,TestClass的接口数量以及接口如下图所示,其中  0&times;0001表示接口数量为1,而0&times;0005表示接口在常量池的索引值,找到常量池的第五个常量,其类型为CONSTANT_Class_info,其  值为:com/ejushang/TestClass/Super

Java Class的文件结构是怎么样的

9)fields_count 和 field_info,  fields_count表示类中field_info表的数量,而field_info表示类的实例变量和类变量,这里需要注意的是  field_info不包含从父类继承过来的字段,field_info的结构如下图所示:

Java Class的文件结构是怎么样的

其中access_flags表示字段的访问标示,比如public,private,protected,static,final等,access_flags的取值如下图所示:

Java Class的文件结构是怎么样的

其中name_index 和  descriptor_index都是常量池的索引值,分别表示字段的名称和字段的描述符,字段的名称容易理解,但是字段的描述符如何理解呢?其实在JVM  规范中,对于字段的描述符规定如下图所示:

Java Class的文件结构是怎么样的

其中大家需要关注一下上图最后一行,它表示的是对一维数组的描述符,对于String[][]的描述符将是[[  Ljava/lang/String,而对于int[][]的描述符为[[I。接下来的attributes_count以及  attribute_info分别表示属性表的数量以及属性表。下面我们还是以上面的TestClass为例,来看看TestClass的字段表吧。

首先我们来看一下字段的数量,TestClass的字段的数量如下图所示:

Java Class的文件结构是怎么样的

从上图中可以看出TestClass有两个字段,查看TestClass的源代码可知,确实也只有两个字段,接下来我们看看第一个字段,我们知道第一个字段应该为private  int staticVar,它在Class文件中的二进制表示如下图所示:

Java Class的文件结构是怎么样的

其中0x001A表示访问标示,通过查看access_flags表可知,其为ACC_PRIVATE,ACC_STATIC,ACC_FINAL,接下  来0&times;0006和0&times;0007分别表示常量池中第6和第7个常量,通过查看常量池可知,其值分别为:staticVar和I,其中staticVar为字  段名称,而I为字段的描述符,通过上面对描述符的解释,I所描述的是int类型的变量,接下来0&times;0001表示staticVar这个字段表中的属性表的  数量,从上图可以staticVar字段对应的属性表有1个,0&times;0008表示常量池中的第8个常量,查看常量池可以得知此属性为  ConstantValue属性,而ConstantValue属性的格式如下图所示:

Java Class的文件结构是怎么样的

其中attribute_name_index表述属性名的常量池索引,本例中为ConstantValue,而ConstantValue的  attribute_length固定长度为2,而constantValue_index表示常量池中的引用,本例中,其中为0&times;0009,查看第9个  常量可以知道,它表示一个类型为CONSTANT_Integer_info的常量,其值为0。

上面说完了private static final int  staticVar=0,下面我们接着说一下TestClass的private int  instanceVar=0,在本例中对instanceVar的二进制表示如下图所示:

Java Class的文件结构是怎么样的

其中0&times;0002表示访问标示为ACC_PRIVATE,0x000A表示字段的名称,它指向常量池中的第10个常量,查看常量池可以知道字段名称为  instanceVar,而0&times;0007表示字段的描述符,它指向常量池中的第7个常量,查看常量池可以知道第7个常量为I,表示类型为  instanceVar的类型为I,最后0&times;0000表示属性表的数量为0.

10)methods_count 和 method_info  ,其中methods_count表示方法的数量,而method_info表示的方法表,其中方法表的结构如下图所示:

Java Class的文件结构是怎么样的

从上图可以看出method_info和field_info的结构是很类似的,方法表的access_flag的所有标志位以及取值如下图所示:

Java Class的文件结构是怎么样的

其中name_index和descriptor_index表示的是方法的名称和描述符,他们分别是指向常量池的索引。这里需要结解释一下方法的描述  符,方法的描述符的结构为:(参数列表)返回值,比如public int instanceMethod(int  param)的描述符为:(I)I,表示带有一个int类型参数且返回值也为int类型的方法,接下来就是属性数量以及属性表了,方法表和字段表虽然都有  属性数量和属性表,但是他们里面所包含的属性是不同。接下来我们就以TestClass来看一下方法表的二进制表示。首先来看一下方法表数量,截图如下:

Java Class的文件结构是怎么样的

从上图可以看出方法表的数量为0&times;0002表示有两个方法,接下来我们来分析第一个方法,我们首先来看一下TestClass的第一个方法的access_flag,name_index,descriptor_index,截图如下:

Java Class的文件结构是怎么样的

从上图可以知道access_flags为0&times;0001,从上面对access_flags标志位的描述,可知方法的access_flags的取值为  ACC_PUBLIC,name_index为0x000B,查看常量池中的第11个常量,知道方法的名称为<init>,0x000C表示  descriptor_index表示常量池中的第12常量,其值为()V,表示<init>方法没有参数和返回值,其实这是编译器自动生成  的实例构造器方法。接下来的0&times;0001表示<init>方法的方法表有1个属性,属性截图如下:

Java Class的文件结构是怎么样的

从上图可以看出0x000D对应的常量池中的常量为Code,表示的方法的Code属性,所以到这里大家应该明白方法的那些代码是存储在Class文件方法表中的属性表中的Code属性中。接下来我们在分析一下Code属性,Code属性的结构如下图所示:

Java Class的文件结构是怎么样的

其中attribute_name_index指向常量池中值为Code的常量,attribute_length的长度表示Code属性表的长度(这里  需要注意的时候长度不包括attribute_name_index和attribute_length的6个字节的长度)。

max_stack表示最大栈深度,虚拟机在运行时根据这个值来分配栈帧中操作数的深度,而max_locals代表了局部变量表的存储空间。

max_locals的单位为slot,slot是虚拟机为局部变量分配内存的最小单元,在运行时,对于不超过32位类型的数据类型,比如  byte,char,int等占用1个slot,而double和Long这种64位的数据类型则需要分配2个slot,另外max_locals的值并  不是所有局部变量所需要的内存数量之和,因为slot是可以重用的,当局部变量超过了它的作用域以后,局部变量所占用的slot就会被重用。

code_length代表了字节码指令的数量,而code表示的时候字节码指令,从上图可以知道code的类型为u1,一个u1类型的取值为0&times;00-0xFF,对应的十进制为0-255,目前虚拟机规范已经定义了200多条指令。

exception_table_length以及exception_table分别代表方法对应的异常信息。

attributes_count和attribute_info分别表示了Code属性中的属性数量和属性表,从这里可以看出Class的文件结构中,属性表是很灵活的,它可以存在于Class文件,方法表,字段表以及Code属性中。

接下来我们继续以上面的例子来分析一下,从上面init方法的Code属性的截图中可以看出,属性表的长度为  0&times;00000026,max_stack的  值为0&times;0002,max_locals的取值为0&times;0001,code_length的长度为0x0000000A,那么00000149h-  00000152h为字节码,接下来exception_table_length的长度为0&times;0000,而attribute_count的值为  0&times;0001,00000157h-00000158h的值为0x000E,它表示常量池中属性的名称,查看常量池得知第14个常量的值为  LineNumberTable,LineNumberTable用于描述java源代码的行号和字节码行号的对应关系,它不是运行时必需的属性,如果通  过-g:none的编译器参数来取消生成这项信息的话,最大的影响就是异常发生的时候,堆栈中不能显示出出错的行号,调试的时候也不能按照源代码来设置断  点,接下来我们再看一下LineNumberTable的结构如下图所示:

Java Class的文件结构是怎么样的

其中attribute_name_index上面已经提到过,表示常量池的索引,attribute_length表示属性长度,而  start_pc和 line_number分表表示字节码的行号和源代码的行号。本例中LineNumberTable属性的字节流如下图所示:

Java Class的文件结构是怎么样的

上面分析完了TestClass的第一个方法,通过同样的方式我们可以分析出TestClass的第二个方法,截图如下:

Java Class的文件结构是怎么样的

其中access_flags为0&times;0001,name_index为0x000F,descriptor_index为0&times;0010,通过查看常量池可  以知道此方法为public int instanceMethod(int  param)方法。通过和上面类似的方法我们可以知道instanceMethod的Code属性为下图所示:

Java Class的文件结构是怎么样的

最后我们来分析一下,Class文件的属性,从00000191h-00000199h为Class文件中的属性表,其中0&times;0011表示属性的名称,查看常量池可以知道属性名称为SourceFile,我们再来看看SourceFile的结构如下图所示:

Java Class的文件结构是怎么样的

其中attribute_length为属性的长度,sourcefile_index指向常量池中值为源代码文件名称的常量,在本例中SourceFile属性截图如下:

Java Class的文件结构是怎么样的

其中attribute_length为0&times;00000002表示长度为2个字节,而soucefile_index的值为0&times;0012,查看常量池的第18个常量可以知道源代码文件的名称为TestClass.java

上述内容就是Java Class的文件结构是怎么样的,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/94842.html

(0)

相关推荐

  • ibatis怎么多条件查询(ibatis优点)

    技术iBATIS发展方向的四个方面分别是什么这期内容当中小编将会给大家带来有关iBATIS发展方向的四个方面分别是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。iBATIS发展方向

    攻略 2021年12月19日
  • MySQL中pt-duplicate-key-checker工具怎么用

    技术MySQL中pt-duplicate-key-checker工具怎么用这篇文章主要为大家展示了“MySQL中pt-duplicate-key-checker工具怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑

    攻略 2021年11月2日
  • 描写女子美貌的句子,形容女子气质智慧美貌的句子

    技术描写女子美貌的句子,形容女子气质智慧美貌的句子1、都说喜欢穿裙子的女人是很浪漫的描写女子美貌的句子,骨子里都是妩媚的,是温婉如水的。我不知道,我只知道我穿裙子是比穿裤子美丽的。只要是美丽,我就会喜欢。MM们,让我们的

    生活 2021年10月19日
  • 如何编写eosjs传输代码

    技术eosjs转账代码怎么写eosjs转账代码怎么写,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。下面介绍使用V20版本的eosjs实现代币转账的思路,并给

    攻略 2021年12月14日
  • Kafka Producer重试参数retries设置取舍的示例分析

    技术Kafka Producer重试参数retries设置取舍的示例分析Kafka Producer重试参数retries设置取舍的示例分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有

    攻略 2021年11月17日
  • 怎么利用python爬取城市公交站点

    技术怎么利用python爬取城市公交站点本篇内容介绍了“怎么利用python爬取城市公交站点”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅

    攻略 2021年12月9日