Class文件结构

来源:互联网 发布:php 实现微信网页授权 编辑:程序博客网 时间:2024/04/29 22:01
Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有任何分隔符,当遇到需要占用8位以上空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储。根据虚拟机规定,Class文件格式采用一种类似于c语言结构体的伪结构来存储数据,这种结构中只有两种数据:无符号数和表。
无符号数是基本类型u1,u2,u4,u8分别代表一个字节,两个字节,四个字节和八个字节,无符号数可以用来描述数字,索引引用,数量值或者按照UTF-8编码构成的字符串。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型。所有的表习惯都已_info结尾。


1.魔数与Class文件的版本
 u4 magic 1
 u2 minor_version 1
 u2 major_version 1
【0123】每个class文件的头四个字节称为魔数,它的唯一作用是确定这个文件是否是一个能被虚拟机接受的Class文件,使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为扩展名可以随意的改动。
【4567】第5678个字节存储的是class文件的版本号,56是次版本78是主版本号,java版本号从45开始,jdk1.7的最大版本号是51,高版本的jdk能兼容以前低版本的class文件,但不能运行更高版本的class文件。 




2.常量池
u2 constant_pool_count 1
cp_info constant_pool  constant_pool_count-1
由于常量池中常量的数目是不固定的,因此在常量池的入口处放置一个u2类型的数据,代表常量池的容量计数值这个容量计数值从1开始,例如该u2为0x0016也就是十进制的22,那么就代表常量池中有21项常量,索引值范围是1-21。


常量池中主要存放两大常量:字面量和符号引用
字面量:例如文本字符串,声明为final的常量值等
符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
java代码在进行javac编译的时候,并不像c c++那样有连接的步骤,而是在虚拟机加载class文件的时候进行动态连接,也就是说class文件不会保存方法和字段的最终内存布局信息,这些方法字段的符号引用不经过运行期转化的话就无法得到真正的内存入口地址,也就无法直接被虚拟机使用,当虚拟机运行时,需要从常量池获取对应的符号引用,再在类创建或者运行时解析翻译到具体的内存地址中。


常量池有11个结构不同的表结构,这几个表结构都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前常量属于哪个类型。


CONSTANT_Utf8_info
类型 名称    数量
u1   tag      1
u2   length   1
u1   bytes   length




3.访问标志
ACC_PUBLIC             0x0001      是否为public类型
ACC_FINAL                 0x0010      是否被声明为final
ACC_SUPER               0x0020      
ACC_INTERFACE       0x0200      标示接口
ACC_ABSTRACT         0x0400      是否为abstract
ACC_SYNTHETIC        0x1000      标志这个类并非由用户代码产生
ACC_ANNOTATION      0x2000       标志注解
ACC_ENUM                    0x4000       标志枚举
 
  
 4.类索引,父类索引与接口索引集合
 
 类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合Class文件中这三个数据信息确定了该类的继承关系。
 类索引:类的全限定名
 父类索引:父类的全限定名(除Object外都不为0)
 接口索引集合:按照implements后的接口顺序从左到右排列在索引集合中。
 
 
 5.字段表集合
 字段表用于描述接口或者类中声明的变量,字段包括类级变量和实例级变量,但不包括方法内部声明的局部变量。
 
 类型           名称                    数量
 u2             access_flags       1
 u2             name_index         1
 u2             descriptor_index   1
 u2             attributes_count   1
 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_VOLATILE        0x0040      字段是否为volatile
    ACC_TRANSIENT       0x0080      字段是否为transient
    ACC_SYNTHETIC    0x1000      是否由编译器自动产生
ACC_ENUM            0x4000      标志枚举
 
    ACC_PUBLIC   ACC_PRIVATE  ACC_PROTECTED最多只能选一个
ACC_FINAL    ACC_VOLATILE 不能同时选择
ACC_PUBLIC   ACC_STATIC   ACC_FINAL 是接口中字段必须有的。
 
描述符的作用是用来描述字段的数据类型,方法的参数列表和返回值
基本数据类型和void都用一个大写字母来表示而对象类型则用L加对象的全限定名来表示。
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 对象类型例如Ljava/lang/Object




String[][]类型的二维数组将被记录为[[Ljava/lang/String;
int[] 将被记录为[I


描述方法时按照先参数列表后参数返回值的顺序描述
void inc() ---------->   ()V
String toString()---->   ()Ljava/lang/String;
int indexOf(char[] source,int start,int count,char[] target,int offset,int tacount,int fromindex)
--------------------->   ([CII[CIII)I






6.方法表集合


类型           名称               数量
 u2             access_flags       1
 u2             name_index         1
 u2             descriptor_index   1
 u2             attributes_count   1
 attribute_info attributes         attributes_count
 
 
 7.属性表集合 
 类型           名称                         数量
  u2            attribute_name_index         1
  u4            attribute_length             1
  u1            info                         attribute_length
  
  7.1Code属性
  java程序方法体中的代码经过javac编译后,最终变成字节码指令存储在Code属性内。
  类型                     名称                    数量
  u2                       attribute_name_index      1 
  u4                       attribute_length          1 
  u2                       max_stack                 1 
  u2                       max_locals                1 
  u4                       code_length               1 
  u1                       code                      code_length
  u2                       exception_table_length    1 
  exception_info           exception_table           exception_table_length
  u2                       attributes_count          1 
  attribute_info           attributes                attributes_count
 
  attribute_name_index指向一个CONSTANT_Utf8_info常量的索引
  attribute_length指示了属性值的长度
  max_stack代表了操作数栈深度的最大值,在方法执行的任意时刻,操作数栈都不会超过这个深度,虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。
  max_locals代表了局部变量所需要的存储空间,单位是Slot。
对于byte char float int short boolean returnAddress等长度不超过32位的数据类型,每个局部变量占1个Slot
double long这两种64位的数据类型则需要2个Slot来存放。 方法参数,异常,方法体中定义的局部变量都需要用局部变量表来存放,但是并不是所有局部变量的和是max_locals,因为局部变量表中的Slot可以重用,当代码超出一个局部变量表的作用域的时候,这个局部变量表所占用的Slot可以被其他的局部变量表所使用,javac编译器会根据局部变量的作用域来分配Slot给各个变量使用,然后计算max_locals的值。
  code_length 和code用来存储java源程序编译后生成的字节码指令。
  code_length代表字节码长度,理论上是u4长度,但其实最大能存储u2长度即65535条字节码指令,如果超过这个限制,javac编译器会拒绝编译。 code是用于存储字节码指令的一系列字节流,每个指令是一个u1类型的单字节,当虚拟机读到code中的一个字节码时,就可以对应的找出这个字节码代表的是什么指令,并且可以知道指令后面是否需要跟随参数,以及参数应当如何理解。
 
 
 
public class TestClass { 
private int m;
public int inc(){
return m+1;
}  
}


javap -verbose TestClass得到如下信息: 
 
{
public TestClass();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 3: 0




public int inc();
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   getfield        #2; //Field m:I
   4:   iconst_1
   5:   iadd
   6:   ireturn
  LineNumberTable:
   line 6: 0 
}
 
 异常表包含四个字段分别是
 类型          名称               数量
 u2            start_pc           1 
 u2            end_pc             1 
 u2            handler_pc         1 
 u2            catch_type         1 
 这些字段的含义是,如果在第start_pc和第end_pc之间出现了类型为catch_pc类型的异常或者其子类则转到handler_pc进行处理,如果catch_type的值是0,代表任意异常情况都走到handler_pc处理。异常表是java代码的一部分,编译器使用异常表而不是简单的跳转命令来实现java异常以及finally处理机制。(在jdk1.4.2之前javac编译器才用了jsr和ret指令实现finally语句,在1.4.2之后改为编译器自动在每段可能的分支路径之后都将finally语句块的内容冗余生成一遍来实现finally语义,在1.7中已经完全禁止出现jsr和ret指令,如果出现这两个指令,虚拟机会在类加载的字节码校验阶段抛出异常)
 
 //方法中带有try catch的分析
 public class TestClass {  
      public static int inc(int n){
     int x;
     try{ 
      x= 1 ; 
     return x;
     }catch(Exception e){
     x = 2; 
     return x;
     }finally{
     x = 3; 
     }
      } 
}






public static int inc(int);
  Code:
   Stack=1, Locals=5, Args_size=1
   0:   iconst_1      //try catch中的x=1 
   1:   istore_1
   2:   iload_1       //保存x到returnvalue中,此时x=1 
   3:   istore_2
   4:   iconst_3      //finally中的x=3 
   5:   istore_1
   6:   iload_2       //将returnvalue中的值放到栈顶,准备给ireturn返回
   7:   ireturn
   8:   astore_2      //给catch语句中定义的exception赋值,存储在Slot2中
   9:   iconst_2      //catch语句块中x=2 
   10:  istore_1
   11:  iload_1       //保存x到returnvalue中,此时x=2
   12:  istore_3
   13:  iconst_3       //finally语句中x=3
   14:  istore_1
   15:  iload_3       //将returnvalue中的值放到栈顶,准备给ireturn返回
   16:  ireturn       
   17:  astore  4      //如果出现异常走到这里
   19:  iconst_3        //finally语句中x=3
   20:  istore_1
   21:  aload   4      //将异常放到栈顶并抛出
   23:  athrow
  Exception table:
   from   to  target type
     0     4     8   Class java/lang/Exception


     0     4    17   any
     8    13    17   any
    17    19    17   any
 
 
 
 
 7.2Exceptions属性
    Exceptions属性的作用是列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。
类型             名称                          数量 
u2               attribute_name_index            1 
    u4               attribute_length                1 
u2               number_of_exceptions            1 
u2               exception_index_table           number_of_exceptions

number_of_exceptions表示方法可能抛出number_of_exceptions种受查异常,每一种受查异常用一个exception_index_table表示,exception_index_table是一个指向常量池中CONSTANT_Class_info常量的索引,代表该受查异常的类型。

 7.3LineNumberTable属性
    类型                    名称                      数量 
u2                   attribute_name_index         1  
u4                   attribute_length             1
u2                   line_number_table_length     1 
line_number_info     line_number_table            line_number_table_length
 
    
    该属性用来描述java源码行号与字节码行号之间的对应关系,它并不是运行时必须的数据,但默认会生成到Class文件中,可以再javac中使用-g:none或者-g:lines选项来取消或者要求生产这项信息,如果选择none,那么堆栈将不会显示出错的行号,调试的时候也不能按照源码来设置断点。
    line_number_table是一个数量为line_number_table_length类型为line_number_info的集合,line_number_info包括了start_pc(字节码行号)和line_number(源码行号)两个u2类型的数据项。
 
 
 7.4LocalVariableTable属性
该属性用于描述栈帧中局部变量表中的变量与java源码中定义的变量之间的关系,它不是运行时的必须属性,但会默认生成到Class文件中,可以在javac中用-g:none或者-g:vars来取消或生成,如果没有这个属性,其他人在引用这个方法时参数名称会丢失,ide使用arg0 arg1之类的占位符代替原有的参数名。
    类型                    名称                          数量 
u2                     attribute_name_index            1  
u4                     attribute_length                1
u2                     local_variable_table_length     1 
local_variable_info    local_variable_table            local_variable_table_length
 
    local_variable_table是一个长度为local_variable_table_length类型为local_variable_info,local_variable_info项目代表了一个栈帧与源码中的局部变量的关联。
local_variable_info的项目结构

    类型                    名称                          数量 
u2                     start_pc                        1  
u2                     length                          1
u2                     name_index                      1
u2                     descriptor_index                1
u2                     index                           1 
    start_pc代表这个局部变量的声明周期开始的字节码偏移量
length表示作用范围覆盖的长度
两者合起来就是这个局部变量在在字节码之中的作用范围
name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。
index是这个局部变量在栈帧局部变量表中Slot的位置,当这个数据类型是64位(double或者long)时,它占用的是index和index+1两个。
 
 7.5SourceFile属性
    该属性用于记录生成这个class文件的源码文件的名称。
可使用javac -g:none或者-g:source来关闭或者生成这项信息
大多数情况下,源文件名称与class文件名称一致,但也有例外如内部类,不生成这项属性时,如果抛出异常,堆栈中不会显示错误代码所属的文件名。
类型                    名称                               数量 
u2                     attribute_name_index                 1  
u2                     attribute_length                     1
u2                     sourcefile_index                     1
    sourcefile_index指向常量池中CONSTANT_Utf8_info常量的索引,常量值是源码文件的名称。



 7.6ConstantValue属性
该属性的作用是通知虚拟机自动为静态变量赋值。
对于实例变量,赋值是在实例构造器<init>方法中进行的
而对于类变量,有两种选择,在类构造器<clinit>方法中进行或者使用ConstantValue属性。
Sun javac编译器的选择是,对于由static 和 final修饰的变量,这个变量的数据类型是基本类型或者String的话,就生成ConstantValue属性来进行初始化。
如果没有final修饰或者并非基本数据类型或者String,将会选择在<clinit>方法中进行初始化。
    
ConstantValue属性结构
类型                  名称                       数量 
u2                    attribute_name_index        1 
u4                    attribute_length            1 
u2                    constantvalue_index         1 

constantvalue_index数据项代表了常量池中的一个字面量常量的引用,根据字段类型的不同,字面量可以是CONSTANT_Long_info   CONSTANT_Float_info  CONSTANT_Double_info  CONSTANT_Integer_info CONSTANT_String_info常量的一种。
 
 7.7InnerClasses属性
该属性用于记录内部类与宿主类之间的关联,如果一个类中定义了一个内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。

InnerClasses属性结构
类型                  名称                      数量  
u2                    attribute_name_index        1 
u4                    attribute_length            1 
    u2                    number_of_classes           1 
inner_class_info      inner_classes               number_of_classes
number_of_classes代表需要记录多少个内部类信息,每一个内部类的信息都由一个inner_class_info表进行描述。

inner_class_info表的结构
类型                    名称                       数量 
u2                      inner_class_info_index      1 
u2                      outer_class_info_index      1 
u2                      inner_name_index            1 
u2                      inner_class_access_flags    1 
inner_class_info_index 和 outer_class_info_index都指向CONSTANT_Class_info型常量,分别代表了内部类和宿主类的符号引用。
inner_name_index指向CONSTANT_Utf8_info型常量的索引,代表内部类的名称,如果是匿名内部类那么这项值为0。
inner_class_access_flags内部类的访问标志。



 7.8Deprecated和Synthetic属性
Deprecated过时
    Synthetic由编译器产生的。
 
 
 7.9StackMapTable属性
该属性会在虚拟机类加载字节码验证阶段被新类型验证器使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
新的验证器在能保证Class文件合法性的前提下,省略了运行时期通过数据流去分析确认字节码的行为逻辑合法性的步骤,而是在编译阶段将一系列的验证类型直接记录在Class文件中,通过检查这些验证类型代替了类型推导过程,从而大幅度提高了字节码验证的性能。
该属性包含多个栈映射帧,每个栈映射帧都代表了一个字节码偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型,类型检查器通过检查目标方法的局部变量和操作数栈所需的类型来确定一段字节码指令是否是符合逻辑约束。

 7.10Signature属性
在jdk1.5之后,任何类,接口,初始化方法或成员的泛型签名如果包含了类型变量或者参数化类型,则Signature属性就会为它记录泛型签名信息,这样做是因为java的泛型采用的是擦除法实现的伪泛型,在字节码中,泛型信息编译之后就会被统统擦除掉,使用擦除是因为实现简单,运行期间也可以省下一些类型所占用的内存空间,坏处是运行期间无法将泛型类型与用户定义的普通类型同等对待,例如运行时期做反射无法得到泛型信息,Signature属性就是为了弥补这个缺陷而增设的,现在的java api能获取反射类型最终的数据来源也是Signature属性。


 7.11BootStrapMethods属性
该属性用于保存invokeddynamic指令引用的引导方法限定符,如果某个类文件结构的常量池中曾经出现过CONSTANT_InvokedDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootStrapMethods属性。
即使CONSTANT_InvokedDynamic_info出现多次,BootStrapMethods 也最多出现一次。
0 0
原创粉丝点击