类文件结构

Java虚拟机学习笔记之类文件结构
什么是字节码

字节码是一种存储格式,它的出现使得虚拟机具有了一种极为重要的中立性质:无关语言,也就是说任何语言在编译成为字节码之后都能够成功运行。诸如Java,JRuby,Groovy,等,他们都能被编译成字节码文件。

而在Java虚拟机中,存储字节码的是.class文件。

Class类文件的结构

Class类文件结构遵守非常严格的规则,按照顺序排列,高位在前。在Class文件中分为两种数据,无符号数与表。

无符号数通常是u1,u2,u4,u8之类的无符号数,用来描述数字,值,索引以及按照UTF-8构成字符串。分别占据一个字节,2个字节……。

通常以_info结尾,.class文件整个就是一个表。

当描述同一类型的数量不定的多个数据时,通常数据前面需要加一个计数器表示有多少个该数据。(之后会用到)

Class文件具体结构
魔数(Magic number)

它用来确定文件是否能够被识别,4个字节大小,class文件的魔数位0xCAFEBABE。

版本号

接下来的四个字节为版本号,前两个为次版本号,后两个为主版本号,java版本号从45开始,如JDK1.1主版本号为45,能够表示45.0-45.65535(2^16),JDK1.2为46.0-46.65535。高版本能够兼容低版本,但是低版本不能识别高版本。

编译器版本与十六进制版本号有明确对应。原因未知,貌似与target参数有关

在这里插入图片描述

常量池

两个版本号之后对应的是常量池,常量池大小不固定,所以第一个u2(两个字节)表示大小,常量池计数从1开始,大小为u1代表的数字-1,因为将0空出来是由原因的,当常量池索引指向表达为不引用时,可将其设置为0。

常量池中存储两种数据,一种为字面量(Literal),类似Java语言中的常量,另一种为符号引用(Symbolic References),符号引用包括类名,接口名,字段名,字段描述符,方法名,方法描述符,它指向常量池中的字面量。

常量池中每一项数据都是一个表,这个表一共有11种,结构不同,但是第一位全是tag,用来标志这是什么类型的表。 在这里插入图片描述

例如在常量池之后的一个u1为07,查表可知为类符号引用,

在这里插入图片描述
之后存储索引,指向一个字面量常量,如0x02,即指向常量池中第二个表中的常量。

那么第二个表中的常量是什么呢。继续读,01是UTF-8编码的字符串,查结构,

在这里插入图片描述

继续,0x001D,表示长度,为29,也就是说,之后的29个字节都是这个字符串,读取翻译便是一个字符串常量。也就是上一个索引所指向的常量。

在Class文件中,方法名字段,也就是索引所指向的东西都需要用utf-8来描述,u2类型最大为65535,所以名字超过64KB就不可以了,另外,在UTF-8编码的字符串采用的UTF-8缩略编码,也就是说0x0001-0x007f(ASCII1-127)都是用一个字节表示,0x0080-0x07ff用两个字节表示,0x0800-0xffff用三个字节表示。

访问标志

在常量池之后的两个字节(16位)紧跟的是访问标志,它用来描述该类的信息,是否为Public,是否是接口等,如果是则为True,相应的对应位为1。最后得到访问标志的结果。

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

访问标志之后是类索引。类索引用来确定类的全限定名,指向一个class_info,进而指向一个字符串。

父类索引,确定父类的全限定名。

接口索引集合,接口可以被实现,和继承,数量不确定,因此第一个u2为计数器确定数字,之后的class_info按顺序对应每一个接口。

字段表集合

字段表(field_info)用来描述类或接口中声名的变量。

在字段表开始的地方首先是u2数据,表示字段表中变量数量,之后的u2表示变量的访问标志,即public,private等,再之后的u2表示是一个索引数据,指向常量池中,是变量的名字。之后的u2是变量的描述符,描述变量是int还是float等,对于数组,根据维数,会在前面加上[,如二维int数组,则为**"[[I"**,如果用描述符来描述方法,则按照下面规则。

(参数描述符)方法返回值的描述符,如void inc()的描述符为()V,int index(int a,int[] b)的描述符为**(I[I)I**.在这里插入图片描述

字段表之后会跟一个属性表,描述变量的属性,例如等于几之类的,如果没有属性,则属性表计数器为0.

字段表集合中不会列出父类的字段,但可能会列出不存在的字段,对于内部类,它的字段表可能含有指向外部类的字段。

Java变量不允许重名,但字节码可以,只要修饰符不一样即可。

方法表

在字段表之后是方法表。方法表的组成几乎同字段表,在方法表开始前有一个计数器,计量有几个方法,之后是每一个方法表,方法表由访问标志,名称索引,描述符,属性表组成,方法的代码部分存在于属性表之中。

同字段表,如果没重写父类方法,就不会产生父类方法信息,但存在自动添加的方法。

Java语言的重载要求特征签名不同,而特征签名在Java语言中指参数不同,但在字节码当中是包括了描述符在内的,也就是说,返回值不同的两个方法依旧可以共存。

属性表

属性表是用来具体描述的。例如方法抛出的异常,方法的代码。变量的取值与特征等等。

1.Code属性 code属性用来具体描述方法

Attribute_Name_index,Code的属性名。u2

Attribute_length,属性的长度,属性值的长度为该长度-6(属性表长度-6)u4

MAX_STACK,max_locals,操作数栈深,局部变量所用空间(以Slot为单位),方法的参数,catch的异常都在局部变量表中。

code_length与code用来描述字节码指令,即真正的方法体代码。每个code为u1,局部变量表中一般自带一个this的Slot,static方法除外。字节码之后是异常处理表。异常处理表告诉了catch的异常类型,try的位置和finally的位置。异常表代表了程序的可能执行方向。

2.Exception属性

这个列举了方法中可能抛出的所有异常。

3.LineNumberTable属性

描述了字节码与源码之间的一一对应的位置关系。

4.LocalVariableTable属性

描述了方法栈帧中的局部变量表与代码中变量的关系。如果省去,在调用方法时会失去参数名称。

LocalVariableTypeTable用在方法使用泛型时与常量池进行连接。

5.SourceFile属性

如其名,描述了生成这个源码的文件名,类名与文件名一致,内部类除外,如果不生产,在抛出异常时会不知道在那个文件出错的。

6.ConstantValue属性

用于描述带有static类型的变量,对于变量的赋值,有 方法可以进行赋值,但对于static变量提供了一种额外的属性来为其赋值,并且该变量必须时基本类型或String类型,

7.InnerClass属性

用来修饰内部类,表示内部类与外部类之间的关系,匿名内部类为在index中为0.

8.Deprecated与Sythetic属性

用来表示代码是否被推荐使用或者是自动生成的,无属性值。

9.StackMapTable属性

属于Code属性中最后一部分的属性表。

用于操作数栈的类型检查。找出操作数指令的偏移量,用于检验是否合逻辑。

可以根据offset_delta+1 计算偏移量。

10.Signature属性

用来记录泛型。

11.BootStrapMethod属性

属于Code属性中最后一部分的属性表。

用于操作数栈的类型检查。找出操作数指令的偏移量,用于检验是否合逻辑。

可以根据offset_delta+1 计算偏移量。

10.Signature属性

用来记录泛型。

11.BootStrapMethod属性

略过。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值