JVM Class类文件结构

Class类文件结构

任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或者接口并不一定都得定义在文件里,譬如类或接口也可以通过类加载器直接生成。

Class文件是一组以8位字节为基础单位的二进制流,整个数据项目严格紧凑的排列在Class文件之中,中间没有添加任何分隔符,这使得Class文件存储的内容几乎都是程序运行必要的数据,没有空隙存在。当遇到需要占用8位字节以上的空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储。

按照Java虚拟机规范的规定,Class文件格式采用C语言结构体的伪结构来存储数据,这种伪结构包含两种数据类型:

  • 无符号数
    • 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1、2、4、8个字节的无符号数,无符号数可用来描述数字、索引引用、数量值和按照UTF-8编码构成字符串值。
    • 表是有多个无符号数或者其他表为数据项构成的复合数据类型,所有的表习惯以_info结尾。表用于描述有层次关系的符合结构的数据,整个Class文件实质上就是一张表,由下表构成

在这里插入图片描述

魔数与Class文件的版本

每个Class文件的头四个字节为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。这个魔数的值为0xCAFEBABE。紧接着魔数的4个字节是Class文件的版本号:第5个和第6个字节是次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。

CAFE BABE 0000 0034 

在这里插入图片描述

常量池

紧接着主次版本号之后的就是常量池,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用了Class文件空间最大的数据项目之一,它还是Class文件中第一个出现的表类型数据项目。

在常量池的入口有一项u2类型的数据,代表着常量池容量计数值。

0039 0A00 0D00 1908

常量池中主要存放两大类常量:

  • 字面量literal
  • 符号引用Symbolic References

字面量比较接近Java语言层面的常量概念,如文本字符串、声明为final的常量值等。
而符号引用则属于编译原理方面的概念,包括了下面的三类常量:

  • 类和接口的权限的名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池中每一项常量都是一个表。常量池的项目类型如下表所示:
在这里插入图片描述

这14类常量类型均有自己的结构。
在这里插入图片描述
tag是标志位,用于区分常量类型;name_index是一个索引值,它指向常量池中一个CONSTANT_UTF8_info类型常量,此常量代表了这个类的全限定名。

下面来看看javap输出的字节码内容。

public class com.jian8.basic.TestCase
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #24.#25        // java/io/PrintStream.println:(I)V
   #4 = Class              #26            // com/jian8/basic/TestCase
   #5 = Class              #27            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/jian8/basic/TestCase;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               i
  #18 = Utf8               I
  #19 = Utf8               SourceFile
  #20 = Utf8               TestCase.java
  #21 = NameAndType        #6:#7          // "<init>":()V
  #22 = Class              #28            // java/lang/System
  #23 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(I)V
  #26 = Utf8               com/jian8/basic/TestCase
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (I)V

上面的代码清单中,我们会发现一些似乎没有在代码中用过的常量,如“I”,“V”,“<init>”,“LineNumberTable”,“LocalVariableTable”等。这部分自动生成的常量与后面将要讲到额字段表、方发表、属性表相关。

访问标志

在常量池结束之后,紧接着的两个字节代表访问标志(flags),这个标志用于识别一些类或者接口层次的访问信息,如下表示:

在这里插入图片描述

类索引、父索引、接口索引集合

类索引、父索引都是一个u2类型的数据。而接口索引集合是一组u2类型的数据的集合,Class文件中由三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名父类索引用于确定这个类的父类全限定名。由于Java语言不支持多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句后的接口顺序从左到右排列在接口索引集合中。

字段表集合

字段表field_info用来描述接口或类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内声明的局部变量。
在这里插入图片描述

这里说明一下“简单名称”,“描述符”,“全限定名”这三种特殊字符串的概念
全限定名和简单名称很好理解。像com/jian8/basic/TestCase就是TestCase这个类的全限定名,仅仅是把类全名中的.换成了/``。简单名称是指没有类型和参数修饰的方法或者字段名称。

描述符是用来描述字段的数据类型、方法数据类型、方法的参数列表(包括数量、类型及顺序)的返回值。
在这里插入图片描述

LineNumberTable属性

用来描述Java源码行号和字节码行号(字节码的偏移量)之间的对应关系。不是运行时不需的属性,但默认会加到Class文件中。可以在javac从中使用-g:none or -g:lines选项取消或要求生成这些信息。

LocalVariableTable属性

用来描述栈帧中局部变量的变量与Java源码定义的变量之间的关系,它也不是运行时不需的属性,但默认会生成到Class文件中,可以在javac从中使用-g:none or -g:vars选项取消或要求生成这些信息。如果没有这个属性,最大的影响就是其他人引用这个方法时,所有的参数名称都会丢失,IDE将会使用诸如arg0,arg1之类的占位符代替原有的参数名,这对程序运行没有影响,但是调试期间无法根据参数名称从上下文获得参数值。

ConstantValue属性

通知虚拟机自动为静态变量赋值,只有static关键字修饰的变量才可以使用这项属性。对于非static类型的变量的复制是在实例构造器方法中进行的,而对于类变量,则有两种方式可以选择:

  • 在类构造器方法中
  • 使用ConstantValue属性

目前Sun javac编译器的选择是:如果同时使用final和static修饰一个变量,并且这个变量的数据类型是基本数据类型或者String的话,就生成ContantValue属性来进行初始化,如果这个变量没有final修饰,或者并非基本类型及字符串,则将会选择在<clinit>方法中进行初始化

InnerClasses属性

用来记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值