Class文件结构

1、概述

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据。

根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表

无符号数:属于基本数据类型,以u1、u2、u4、u8来分别代表1、2、4、8个字节的无符号数。无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码的字符串值。

表:是由多个无符号数或其他表作为数据项构成的符合数据类型,所有的表都习惯性地以“_info”结尾。

整个Class文件本质就是一张表。


接下来后来看看Class文件中按照严格的顺序排列的字节流都具体包含些什么数据:


有一点我们需要注意,比如cp_info,cp_info表示常量池,上图中用constant_pool[constant_pool_count-1]的方式来表示常量池有constant_pool_count-1个常量,它这里是采用数组的表现形式,但是大家不要误以为所有的常量池的常量长度都是一样的,其实这个地方只是为了方便描述采用了数组的方式,但是这里并不像编程语言那里,一个int型的数组,每个int长度都一样。明确了这一点以后,我们在回过头来看看上图中每一项都具体代表了什么含义。


下面首先给出一段简单的Java程序,以这段代码编译成的Class文件为基础进行Class文件结构的介绍,代码如下:

public class TestClass{  
    private int num;  
      
    public int inc(){  
        for(int i=0; i<10; i++){  
            num = i;  
        }  
        return num;  
    }  
      
    public static void main(String[] args){  
        new TestClass().inc();  
    }  
      
} 
把代码编译成Class文件后,使用16进制编辑器WinHex打开这个Class文件,可以看到Class的内部结构如下:



2、魔数与Class文件的版本

Class文件格式表中,第一项为一个u4类型的名为magic的数据项。这是因为每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用就是用来确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来进行身份识别。例如图片格式。使用魔数而不是扩展名来进行识别主要是基于安全考虑,因为文件扩展名可以很随便地被人为改动。Class文件的魔数的值为:0xCAFFBABE。class文件魔数值如下图所示:
 

紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6字节是次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。

上图代表版本号的第5个字节和第6个字节值为0x0000,而主版本号的值为0x0032,即十进制的50


3、常量池
紧接着主次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据类型。常量池之中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:
   1.类和接口的全限定名(Fully Qualified Name)
   2.字段的名称和描述符(Descriptor)
   3.方法的名称和描述符。
 
使用Javap命令输出常量表:

<span style="font-size:14px;">D:\JVM>javap -verbose TestClass  
Compiled from "TestClass.java"  
public class TestClass extends java.lang.Object  
  SourceFile: "TestClass.java"  
  minor version: 0  
  major version: 50  
  Constant pool:  
const #1 = Method       #6.#20; //  java/lang/Object."<init>":()V  
const #2 = Field        #3.#21; //  TestClass.num:I  
const #3 = class        #22;    //  TestClass  
const #4 = Method       #3.#20; //  TestClass."<init>":()V  
const #5 = Method       #3.#23; //  TestClass.inc:()I  
const #6 = class        #24;    //  java/lang/Object  
const #7 = Asciz        num;  
const #8 = Asciz        I;  
const #9 = Asciz        <init>;  
const #10 = Asciz       ()V;  
const #11 = Asciz       Code;  
const #12 = Asciz       LineNumberTable;  
const #13 = Asciz       inc;  
const #14 = Asciz       ()I;  
const #15 = Asciz       StackMapTable;  
const #16 = Asciz       main;  
const #17 = Asciz       ([Ljava/lang/String;)V;  
const #18 = Asciz       SourceFile;  
const #19 = Asciz       TestClass.java;  
const #20 = NameAndType #9:#10;//  "<init>":()V  
const #21 = NameAndType #7:#8;//  num:I  
const #22 = Asciz       TestClass;  
const #23 = NameAndType #13:#14;//  inc:()I  
const #24 = Asciz       java/lang/Object;  
  
{  
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 1: 0  
  
  
public int inc();  
  Code:  
   Stack=2, Locals=2, Args_size=1  
   0:   iconst_0  
   1:   istore_1  
   2:   iload_1  
   3:   bipush  10  
   5:   if_icmpge       19  
   8:   aload_0  
   9:   iload_1  
   10:  putfield        #2; //Field num:I  
   13:  iinc    1, 1  
   16:  goto    2  
   19:  aload_0  
   20:  getfield        #2; //Field num:I  
   23:  ireturn  
  LineNumberTable:  
   line 5: 0  
   line 6: 8  
   line 5: 13  
   line 8: 19  
  
  StackMapTable: number_of_entries = 2  
   frame_type = 252 /* append */  
     offset_delta = 2  
     locals = [ int ]  
   frame_type = 250 /* chop */  
     offset_delta = 16  
  
  
public static void main(java.lang.String[]);  
  Code:  
   Stack=2, Locals=1, Args_size=1  
   0:   new     #3; //class TestClass  
   3:   dup  
   4:   invokespecial   #4; //Method "<init>":()V  
   7:   invokevirtual   #5; //Method inc:()I  
   10:  pop  
   11:  return  
  LineNumberTable:  
   line 12: 0  
   line 13: 11  
  
  
}  </span>


4 访问标志
   在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是接口还是类;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否声明为final,等等。
 
5 类索引、父类索引与接口索引集合
   类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。Java不允许多重继承,所以父类索引只有一个,除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,所有被实现的接口按类定义中的implements(如果类是一个接口则是extends)后的接口顺序从左到右排列在接口的索引集合中。
 
6 字段表集合
   字段表(field_info)用于描述接口或类中声明的变量。字段(field)包括了类级变量和实例级变量,但不包括方法内部声明的变量。一个字段的信息包括:作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否序列化(transient修饰符)、字段数据类型(基本数据类型、对象、数组)、字段名称。这些信息中,各个修饰符都是布尔值,要么有,要么没有。
   全限定名称:如果TestClass类是定义在com.chenzhou.jvm包中,那么这个类的全限定名为com/chenzhou/jvm/TestClass。
   简单名称:简单名称指没有类型和参数修饰的方法或字段名称,在上面的例子中,TestClass类中的inc()方法和num字段的简单名称分别为“inc”和“num”。
   描述符:描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte,char,double,float,int,long,short,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示,如下图所示:


7 方法表集合
    Class文件存储格式中对方法的描述与对字段的描述几乎完全一致。方法表的结构如同字段表一样,一次包括了访问标志、名称索引、描述符索引、属性表集合几项。
 

8 属性表集合
    Java虚拟机规范第二版中预定义了9项虚拟机应当能识别的属性,包括:Code、ConstantValue、Deprecated、Exceptions、InnerClasses、LineNumberTable、LocalVariableTable、SourceFile、Synthetic。


参考来源:

JVM学习笔记(四):Class文件结构

Class文件内容及常量池



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值