bboyjing's blog

自己动手写JVM五【解析class文件(三)】

本章节继续学习ClassFile中各项所代表的含义。

接口索引表

类和超类索引后面时接口索引表,表中存放的也是常量池索引。ClassFile.class没有实现接口,所以接口表是空的:
jvmgo_10
在class_file.go文件中添加读取接口索引表的方法:

1
2
3
4
func (self *ClassFile) readInterface(reader *ClassReader) {
//接口是u2类型的表结构,所以用的readUint16s方法读取
self.interfaces = reader.readUint16s()
}

字段表(field_info)和方法表(method_info)

接口索引表之后是字段表和方法表,分别存储字段和方法信息。field_info和method_info的基本结构大致相同,差别仅在于属性表,下面时Java虚拟机规范给出的field_info结构定义:

1
2
3
4
5
6
7
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}

method_info的结构上述一样,仅在access_flags和attribute_info的可选项中有所区别。为了避免重复代码,所以用一个结构体同一表示字段表和方法表,位于member_info.go文件中,结构体名为MemberInfo:

1
2
3
4
5
6
7
type MemberInfo struct {
cp ConstantPool
accessFlags uint16
nameIndex uint16
descriptorIndex uint16
attributes []AttributeInfo
}

MemberInfo中有4个字段和field_info完全对应,请中cp字段用于保存常量池指针,后面会用到。我们下面来看下field_info中各项的含义。

类型 名称 数量 含义
u2 access_flags 1 字段和方法的访问修饰符
u2 name_index 1 常量池索引,代表字段的简单名称
u2 descriptor_index 1 常量池索引,代表字段和方法的描述符
u2 attributes_count 1 字段和方法的额外附加属性数量
attribute_info attributes[attributes_count] attributes_count 字段和方法的额外的附加属性

访问修饰符和上一节讲的类访问符标志很相似,下表列举下字段的访问标志可选项:

标志名称 标志值 含义
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类型

在实际情况中访问标志的使用时有限制的,比如ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED三个标志只能选其一,ACC_FINAL和ACC_VOLATILE只能选其一等等,这些都是由Java本身的语言规范所决定的。
方法表的访问修饰符和字段表访问修饰符类似:

标志名称 标志值 含义
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_NAVIVE 0x0100 方法是否为native
ACC_ABSTRACT 0x0400 方法是否为abstract
ACC_STRICTFP 0x0800 方法是否为strictfp
ACC_SYNTHETIC 0x1000 方法是否由编译器自动产生

ClassFile.class的字段表和方法表如下:
jvmgo_11
jvmgo_12

在member_info.go文件中添加读取方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func readMembers(reader *ClassReader, cp ConstantPool) []*MemberInfo {
// 获取fields_count或者methods_count
memberCount := reader.readUint16()
members := make([]*MemberInfo, memberCount)
// 读取每一个field或者method
for i := range members {
members[i] = readMember(reader, cp)
}
return members
}
func readMember(reader *ClassReader, cp ConstantPool) *MemberInfo {
// 读取class文件中对应的字节
return &MemberInfo{
cp: cp,
accessFlags: reader.readUint16(),
nameIndex: reader.readUint16(),
descriptorIndex: reader.readUint16(),
// 读取属性表后面再讲
//attributes: readAttributes(reader, cp),
}
}

在class_file.go中添加读取字段表和方法表的方法:

1
2
3
4
5
6
7
func (self *ClassFile) readFields(reader *ClassReader) {
self.fields = readMembers(reader, self.constantPool)
}
func (self *ClassFile) readMethods(reader *ClassReader) {
self.methods = readMembers(reader, self.constantPool)
}

下节继续…