jvm类文件的结构4-字段表集合和方法表集合

字段表集合

字段表集合的信息

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

字段(field包括了类级变量或实例级变量,但不包括方法内部声明的变量)

Java中描述一个字段包含以下信息:

  1. 字段的作用域(public、pivate、 protected修饰符)

  2. 类级变量还是实例级变量(static修饰符)

  3. 可变性(final)

  4. 并发可见性(volatile修饰符,是否强制从主内存读写)

  5. 可否序列化(transient修饰符)

  6. 字段数据类型(基本类型、对象、数组)、字段名称。

这些信息大概可分为两类:

  1. 修饰符. 修饰符只有有和无两种状态,适合使用标志位表示

  2. 无法确定的信息。引用常量池中的常量来描述。

字段表集合的结构

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

access_flags

字段修饰符在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 字段是否enum

name_index和descriptor_index

它们都是对常量池的引用,分别代表着字段的简单名称及字段和方法的描述符。

全限定名、简单名称、描述符
权限定名

格式如: org/fenixsoft/clazz/TestClass。将类全名的”.”替换成了”/“,为了使连续的多个全限定名之间不产生混淆,在使用时最后会加上;

简单名称

简单名称就是指没有类型和参数修饰的方法或字段名称,这个类中的inc()方法和m字段的简单名称分别是”inc”和”m”。

描述符

用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。

根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示。

标识字符 含义
B 基本类型byte
C 基本类型char
D 基本类型double
F 基本类型float
I 基本类型int
J 基本类型long
S 基本类型short
Z 基本类型boolean
V 特殊类型void
L 对象类型,如Ljava/lang/Object

对于数组类型,每一维度将使用一个前置的”[“字符来描述

如一个定义为”java.lang.String[][]“类型的二维数组,将被记录为: “[[Ljava/lang/String;”

一个整型数组”int[]”将被记录为”[I”。

用描述符描述方法时,按照先参数列表,后返回值的顺序。参数列表按照参数的严格顺序放在一组小括号”()”之内。

如方法void inc()的描述符为”()V”

方法java.lang.String toString()的描述符为”()Ljava/lang/String;”

方法int indexOf(char[] soure, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为”([CII[CIII)I”。

attribute_info

将在属性表中详细讲解

字段表总结

字段表集合中不会列出超类或父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。

方法表集合

方法表描述

方法表描述和字段的描述基本一致,包括了:

  • 访问标志(access_flags)

  • 名称索引(name_index)

  • 描述符索引(descriptor_index)

  • 属性表集合(attributes)

不同之处主要在于访问标志和属性表集合的可选项。

方法中没有 volatiletransient,但是多了 synchronizednativestrictfpabstract。如下:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 方法是否为public
ACC_PRIVATE 0x0002 方法是否为private
ACC_PROTECTED 0x0004 方法是否为protected
ACC_STATIC 0x0008 方法是否为static
ACC_FINAL 0x0010 方法是否为final
ACC_SYNCHRONIZED 0x0020 方法是否为synchronized
ACC_BRIDGE 0x0040 方法是否为编译器产生的桥接方法
ACC_VARARGS 0x0080 方法是否接受不定参数
ACC_NATIVE 0x0100 方法是否为native
ACC_ABSTRACT 0x0400 方法是否为abstract
ACC_STRICT 0x0800 方法是否为strictfp
ACC_SYNTHETIC 0x1000 方法是否为编译器自动产生的

而方法里的Java代码,经过编译器编译成字节码指令之后,存放在方法属性表集合中的一个名为”Code”的属性里面。

方法表总结

如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。 但有可能会出现由编译器自动添加的方法,最典型的是类构造器”\<clinit>“方法和实例构造器”\<init>“方法。

在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名。
特征签名就是各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里面是无法仅仅依靠返回值的不同来对一个已有方法进行重载的。
但是在Class文件格式之中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存。 也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中的。