JAVA类文件结构(一)

Java技术能够一直保持良好的想后兼容,类文件结构是工功不可没的。类文件结构大多数都在在《Java虚拟机规范第一版》中已经定义好的。虽然发展到现在经历了十几个大版本和几个小版本,但是类文件结构细节几乎没有发生什么改变。尽管不同版本的《Java虚拟机规范》都对类结构有改进,但是都是进行扩充,原来定义好的都没有发生改变

根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数 据,这种伪结构中只有两种数据类型:“无符号数”和“表”。后面的解析都要以这两种数据类型为基础,

·无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个 字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。表是由多个无符号数或者其他表作为数据项构成的复合数据类型

类型

名称

数量

u4

magic

1

u2

minor_version

1

u2

major_version

1

u2

constant_pool_count

1

cp_info

constant_pool

constant_pool_count

u2

access_flags

1

u2

this_class

1

u2

super_class

1

u2

interfaces_count

1

u2

interfaces

interfaces_count

u2

fields_count

1

field_info

fields

fields_count

u2

methods_count

1

mothod_info

methods

methods_count

u2

attributes_count

1

attribute_info

attributes

attributes_count

无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的 容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的“集 合”。

Class的结构不像XML等描述语言,由于它没有任何分隔符 号,所以在表6-1中的数据项,无论是顺序还是数量,甚至于数据存储的字节序(Byte Ordering,Class 文件中字节序为Big-Endian)这样的细节,都是被严格限定的,哪个字节代表什么含义,长度是多少, 先后顺序如何,全部都不允许改变

示例代码

下面说讲解的内容都是以改示例代码为基础

源码
package org.test;

public class Test{
	
	private int value=0;
	
	public void setValue(int value){
		this.value=value;
	}
	
	public int getValue(){
		return value;
	}
	
	public void autoIncrement(){
		++value;
	}		
	
}
字节码
Compiled from "Test.java"
public class org.test.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // org/test/Test.value:I
   #3 = Class              #20            // org/test/Test
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               value
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               setValue
  #12 = Utf8               (I)V
  #13 = Utf8               getValue
  #14 = Utf8               ()I
  #15 = Utf8               autoIncrement
  #16 = Utf8               SourceFile
  #17 = Utf8               Test.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // value:I
  #20 = Utf8               org/test/Test
  #21 = Utf8               java/lang/Object
{
  public org.test.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_0
         6: putfield      #2                  // Field value:I
         9: return
      LineNumberTable:
        line 3: 0
        line 5: 4

  public void setValue(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field value:I
         5: return
      LineNumberTable:
        line 8: 0
        line 9: 5

  public int getValue();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field value:I
         4: ireturn
      LineNumberTable:
        line 12: 0

  public void autoIncrement();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field value:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field value:I
        10: return
      LineNumberTable:
        line 16: 0
        line 17: 10
}
SourceFile: "Test.java"
hex
CAFEBABE0000003400160A00040012090003001307001407001501000576616C7565010001490100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C6501000873657456616C75650100042849295601000867657456616C756501000328294901000D6175746F496E6372656D656E7401000A536F7572636546696C65010009546573742E6A6176610C000700080C0005000601000D6F72672F746573742F546573740100106A6176612F6C616E672F4F626A65637400210003000400000001000200050006000000040001000700080001000900000026000200010000000A2AB700012A03B50002B100000001000A0000000A000200000003000400050001000B000C000100090000002200020002000000062A1BB50002B100000001000A0000000A000200000008000500090001000D000E000100090000001D00010001000000052AB40002AC00000001000A0000000600010000000C0001000F00080001000900000027000300010000000B2A59B400020460B50002B100000001000A0000000A000200000010000A001100010010000000020011

1. 魔数与Class文件的版本

每个Class文件的头4个字节被称为魔术,它的唯一作用是确定这个文件是否为一个被虚拟机接受的文件。除了Java还有很多文件格式标准中都是用了魔数,如Gif或者JEPG等文件头中都有魔数。java文件的魔数为0xCAFEBABE。
紧接着魔数的4个字节是Class的版本号:第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)如上面的示例代码中Hex 前4个字节分别是CAFEBABE,后面紧跟着次版本号占两个字节 0x0000,再后面是主版本号0x0034,查询资料0x0034为JAVA8

2.常量池

在主次版本之后是常量池入口,此处是一个u2类型的数据,表示常量池中数据的格式。此处需要注意常量池数量是从1开始计数的也就是当这个位置是0x0001时没有表示常量池中数据数量为0。这样做的目的在于,如果后面某些指向常量池的索引值的数据在特定情况下 需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。上面的示例中常量池容量(偏移地址:0x00000008)是:0x0016(十进制 22),表示常量池中有21项常量

常量池中主要放两类常量:字面量(Literal),符号引用(Symbolic References)

  1. 字面量
    接近Java语言层面的常量概念,如文本字符串,被声明为final的常量值
  2. 符号常量
    属于编译原理方面的概念主要包含一下几种
  • 被模块导出或者开放的包
  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
  • 方法句柄和方法类型
  • 动态调用点和动态常量

常量池每一项常量都是一个表,最初表中有11项结构不同的表后来又扩充了4中动态语言相关的常量,为了支持java模块化系统又加入了两个常量。17类常量都有同一个特点,表的起始第一位是一个u1类型的标志位代表常量属于那种常量

常量池中17种数据类型表结构
  1. CONSTANT_Utf8_info

项目

类型

描述

tag

u1

值为1

length

u2

UTF-8编码的字符串占用的字节数

byte

u2

长度为length的UTF-8编码的字符串

  1. CONSTANT_Integer_info

项目

类型

描述

tag

u1

值为3

byte

u4

按高位在前储存的int值

  1. CONSTANT_Float_info

项目

类型

描述

tag

u1

值为4

byte

u4

按高位在前储存的float值

  1. CONSTANT_Long_info

项目

类型

描述

tag

u1

值为5

byte

u4

按高位在前储存的long值

  1. CONSTANT_Double_info

项目

类型

描述

tag

u1

值为6

byte

u4

按高位在前储存的double值

  1. CONSTANT_Class_info

项目

类型

描述

tag

u1

值为7

index

u2

指向全限定常量的索引

  1. CONSTANT_String_info

项目

类型

描述

tag

u1

值为8

index

u2

指向字符串字面量的索引

  1. CONSTANT_Fieldref_info

项目

类型

描述

tag

u1

值为9

index

u2

指向声明字段的类或接口描述符CONSTANT_Class_info

index

u2

指向字段描述符CONSTANT_NameAndType的索引

  1. CONSTANT_Methodref_info

项目

类型

描述

tag

u1

值为10

index

u2

指向声明字段的类或接口描述符CONSTANT_Class_info

index

u2

指向字段描述符CONSTANT_NameAndType的索引

  1. CONSTANT_InterfaceMethodref_info

项目

类型

描述

tag

u1

值为11

index

u2

指向声明字段的类或接口描述符CONSTANT_Class_info

index

u2

指向字段描述符CONSTANT_NameAndType的索引

  1. CONSTANT_NameAndType_info

项目

类型

描述

tag

u1

值为12

index

u2

指向该字段或方法名称的索引

index

u2

指向该字段或方法描述符常量的索引

  1. CONSTANT_MethodHandle_info

项目

类型

描述

tag

u1

值为15

reference_kind

u1

值必须在1-0之间(包括1,9)它决定了方法句柄类型。方法句柄类型的值表示方法句柄的字节码行为

reference_index

u2

值必须是对常量池的有效索引

  1. CONSTANT_MethodType_info

项目

类型

描述

tag

u1

值为16

descriptor

u2

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

  1. CONSTANT_Dynamic_info

项目

类型

描述

tag

u1

值为17

bootstrap_method_attr_index

u2

值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引

name_and_type_index

u2

值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

  1. CONSTANT_InvokeDynamic_info

项目

类型

描述

tag

u1

值为18

bootstrap_method_attr_index

u2

值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引

name_and_type_index

u2

值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

  1. CONSTANT_Module_info

项目

类型

描述

tag

u1

值为19

name_index

u2

值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示模块名称

  1. CONSTANT_Package_info

项目

类型

描述

tag

u1

值为20

name_index

u2

值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示包名称

回过头我们在分析一下上面的示例代码

第一项

在常量池数量后的是0x0A(位置:偏移量0x000A),查找上面常量池项目类型0x0A对应的是CONSTANS_Methodref_info 它一共占用5个字节所以就是0x0A 0004 0012 查表可知0x0004表示的是类或接口描述符的索引, 0x0012 表示字段描述符 索引。查看示例代码中字节码常量池中第4项是一个Class项,第 0x0012项是一个NameAndType

第二项

第二项是0x09(位置:偏移量0x000F),查表可知0x09是CONSTANT_Fieldref_info, 一共占5个字节0x09 0003 0013。
第一个index CONSTANT_Class_info的索引是0x03,查示例代码中字节码可知常量池第三个项Class。
第二个index:CONSTANT_NameAndType_info的索引是0x0013,查看示例代码中字节码可知常量中第0x0013项是NameAndType

第三项

第三项0x07(位置:偏移量0x0014),查表可知0x07是CONSTANT_Class_info ,一共占3个字节 0x07 0014。
index: 全限定常量名常量项的索引 0014 (是一个CONSTANST_Utf8_info类型的表),查示例代码中字节码可知常量池中第0x0014项是 org/test/Test。

第四项

第四项0x07(位置:偏移量0x0017),,查表可知0x07是CONSTANT_Class_info ,一共占3个字节 0x07 0015
index: 全限定常量名常量项的索引 0014 (是一个CONSTANST_Utf8_info类型的表),查示例代码中字节码可知常量池中第0x0014项是 java/lang/Object。

第五项

第五项0x01(位置:偏移量0x001A),查表可知0x01是CONSTANT_Utf8_info,查表可知 length 占2个字节是0x0005,
所以一个bytes是一个字节一共又length(0x0005)个,第五项一共占了8个字节(u1+u2+u1*5=u8) 0x01 0005 76616C7565 。
76 61 6C 75 65转换成字符串(Assic)"value"

第六项

第六项0x01(位置:偏移量0x0022),查表可知0x01是CONSTANT_Utf8_info,查表可知 length 占2个字节是0x0001,
所以一个bytes是一个字节一共又length(0x0001)个,第五项一共占了4个字节(u1+u2+u1*1=u4) 0x01 0001 49 。
49转换成字符串(Assic)"I"

第七项

第七项0x01(位置:偏移量0x0026),查表可知0x01是CONSTANT_Utf8_info,查表可知 length 占2个字节是0x0006,
所以一个bytes是一个字节一共又length(0x0001)个,第五项一共占了9个字节(u1+u2+u1*6=u9) 0x 01 0006 3C696E69743E 。
3C 69 6E 69 74 3E转换成字符串(Assic)"< init >"

第八项

第八项0x01(位置:偏移量0x002F),查表可知0x01是CONSTANT_Utf8_info,查表可知 length 占2个字节是0x0003,
所以一个bytes是一个字节一共又length(0x0003)个,第五项一共占了5个字节(u1+u2+u1*3=u5) 0x 01 0003 282956 。
28 29 56转换成字符串(Assic)"()V"

第九项

第九项0x01(位置:偏移量0x0035),和上面差不多,一共占7字节 为0x01 0004 436F6465,bytes为"Code"

第十项

第十项0x01(位置:偏移量0x003C),和上面差不多,一共占0x12字节 为0x01 000F4 C696E654E756D6265725461626C65,bytes为Code ""LineNumberTable"

第十一项

第十一项0x01(位置:偏移量0x004E),和上面差不多,一共占0x0B字节 为0x01 0008 73657456616C7565,bytes为"setValue"

第十二项

第十二项0x01(位置:偏移量0x0059),和上面差不多,一共占0x07字节 为0x01 0004 28492956,bytes为"(I)V"

第十三项

第十三项0x01(位置:偏移量0x0060),和上面差不多,一共占0x0B字节 为0x01 0008 67657456616C7565,bytes为”getValue”

第十四项

第十四项0x01(位置:偏移量0x006b),和上面差不多,一共占0x06字节 为0x01 0003 282949,bytes为”()I”

第十五项

第十五项0x01(位置:偏移量0x0071),和上面差不多,一共占0x10字节 为0x01 000D 6175746F496E6372656D656E74,bytes为”autoIncrement"

第十六项

第十六项0x01(位置:偏移量0x0081),和上面差不多,一共占0x0D字节 为0x01 000A 536F7572636546696C65,bytes为”SourceFile"

第十七项

第十七项0x01(位置:偏移量0x008E),和上面差不多,一共占0x0C字节 为0x01 0009 546573742E6A617661,bytes为”Test.java"

第十八项

第十八项0x0C(位置:偏移量0x009A),查表可知0x0C是CONSTANT_MethodHandle_info,完整信息0x0C 0007 0008。查表可知 :
第一个index:占2个字节是0x0007,常量池的0x07项CONSTANT_Utf8_info 是 "< init >"

第二个index:占2个字节0x0008,常量池的0x08项CONSTANT_Utf8_info 是 "()V"

第十九项

第十八项0x0C(位置:偏移量0x009F),查表可
知0x0C是CONSTANT_MethodHandle_info,完整信息0x0C 0005 0006。
第一个index:占2个字节是0x0005,常量池的0x05项CONSTANT_Utf8_info 是 "value"

第二个index:占2个字节0x0006,常量池的0x06项CONSTANT_Utf8_info 是 "I"

第二十项

第十四项0x01(位置:偏移量0x00A4),和上面差不多,一共占0x10字节 为0x01 000D 6F72672F746573742F54657374,bytes为”org/test/Test”

第二十一项

第十四项0x01(位置:偏移量0x00A4),和上面差不多,一共占0x10字节 为0x01 0010 6A6176612F6C616E672F4F626A656374,bytes为”java/lang/Object”

到此位置常量池部分字节码Hex就分析完了,下一节在常量池之后的字节码

如有错误希望您指出,我将及时修改更新。


“路漫漫其修远兮,吾将上下而求索”