bboyjing's blog

自己动手写JVM十八【类和对象(一)】

本章将实现线程共享的运行时数据区,包括方法去和运行时常量池。将进一步处理ClassFile结构体,把它加以转换,放进方法去以后供后续使用。还会初步讨论类和对象的设计,实现一个简单的类加载器,并且实现类和对象相关的部分指令。本章实现代码位于/rtdata/heap包下,先将之间定义的object.go文件移到heap包下,注意修改包名,以及使用到Object结构体的文件(slot.go、local_vars.go、operand_stack.go)。

方法区

方法区是运行时数据区的一块逻辑区域,由多个线程共享。方法区主要存放从class文件获取的类信息,此外,类变量也存放在方法区中。当Java虚拟机第一次使用某个类时,它会搜索类路径,找到相应的class文件,然后读取并解析class文件,把相关信息放进方法区。至于方法区到底位于何处,是固定大小还是动态调整,是否参与垃圾回收,以及如何在方法区内存放类数据等,Java虚拟机规范并没有明确规定。下面先来看看有哪些信息需要放进方法区。

类信息

使用结构体来表示要放进方法区内的信息,结构体位于class.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Class struct {
accessFlags uint16
name string
superClassName string
interfaceNames []string
constantPool *ConstantPool
fields []*Field
methods []*Method
loader *ClassLoader
superClass *Class
interfaces []*Class
instanceSlotCount uint
staticSlotCount uint
staticVars Slots
}

我们逐个来看下Class结构体中每一项代表什么。

  • accessFlags是类的访问标志,总共16bit。在解析class文件章节的类访问标志中已经讲过,这里不赘述了,我们把各个标志作为常量定义到access_flags.go文件中,代码就不贴出来了。
  • name存放类的名称,格式类似java/lang/Object。
  • superClassName存放超类的名称,格式类似java/lang/Object。
  • interfaceNames存放接口的名称数组,格式类似[java/lang/Object,…]。
  • constantPool存放运行时常量池,后面再详细讲。
  • fields存放字段表
  • methods存放方法表
  • loader存放类加载器指针
  • superClass存放类的超类指针
  • interfaces存放类的接口指针
  • instanceSlotCount存放实例变量占据的空间大小
  • staticSlotCount类变量占据的空间大小
  • staticVars存放静态变量

其中accessFlags、name、superClassName、interfaceNames、constantPool可以直接从ClassFile中读取,其余剩下的字段下面会逐一学习,等都学完再回过来完善class.go文件。

字段信息

字段和方法都属于类的成员,它们有一些相同的信息(访问标志、名字、描述符)。为了避免重复代码,创建一个结构体存放这些信息,该结构体位于class_member.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 字段和方法公用结构体
type ClassMember struct {
// 访问标志
accessFlags uint16
// 名字
name string
// 描述符
descriptor string
// 当前类指针,可以通过字段或方法访问到它所属的类
class *Class
}
// 从class文件中复制数据
func (self *ClassMember) copyMemberInfo(memberInfo *classfile.MemberInfo) {
self.accessFlags = memberInfo.AccessFlags()
self.name = memberInfo.Name()
self.descriptor = memberInfo.Descriptor()
}
// 省略判断访问标识和属性的Getter方法,参照项目源码

字段信息结构体位于filed.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 字段信息结构体
type Field struct {
// 字段和方法公用结构体
ClassMember
}
// 初始化字段信息
func newFields(class *Class, cfFields []*classfile.MemberInfo) []*Field {
fields := make([]*Field, len(cfFields))
for i, cfField := range cfFields {
fields[i] = &Field{}
fields[i].class = class
fields[i].copyMemberInfo(cfField)
}
return fields
}
// 省略判断访问标识的方法,参照项目源码

方法信息

方法比字段稍微复杂一些,因为方法中有字节码,实现代码位于method.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type Method struct {
// 字段和方法公用结构体
ClassMember
// 操作数栈最大深度
maxStack uint
// 局部变量表大小
maxLocals uint
// 字节码
code []byte
}
// 初始化方法信息
func newMethods(class *Class, cfMethods []*classfile.MemberInfo) []*Method {
methods := make([]*Method, len(cfMethods))
for i, cfMethod := range cfMethods {
methods[i] = &Method{}
methods[i].class = class
methods[i].copyMemberInfo(cfMethod)
methods[i].copyAttributes(cfMethod)
}
return methods
}
// 从方法的属性中读取maxStack、maxLocals和字节码
func (self *Method) copyAttributes(cfMethod *classfile.MemberInfo) {
if codeAttr := cfMethod.CodeAttribute(); codeAttr != nil {
self.maxStack = codeAttr.MaxStack()
self.maxLocals = codeAttr.MaxLocals()
self.code = codeAttr.Code()
}
}

运行时常量池

运行时常量池主要存放两类信息:字面量和符号引用。字面量包括整数、浮点数和字符串字面量;符号引用包括类符号引用、字段符号引用、方法符号引用和接口方法符号引用。运行时常量池结构体位于constant_pool.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 常量接口
type Constant interface{}
// 运行时常量池结构体
type ConstantPool struct {
class *Class
consts []Constant
}
ni
// 把class文件中的常量池转换成运行时常量池
func newConstantPool(class *Class, cfCp classfile.ConstantPool) *ConstantPool {
// 获取常量池中常量数量
cpCount := len(cfCp)
// 构造运行时常量池结构体
consts := make([]Constant, cpCount)
rtCp := &ConstantPool{class, consts}
// 遍历class文件常量池
for i := 1; i < cpCount; i++ {
cpInfo := cfCp[i]
switch cpInfo.(type) {
// 根据常量类型不同,读取方式不一样
case *classfile.ConstantIntegerInfo:
intInfo := cpInfo.(*classfile.ConstantIntegerInfo)
consts[i] = intInfo.Value()
// 省略其他case,参照项目源代码
...
default:
// todo
}
}
return rtCp
}
// 根据索引返回运行时常量
func (self *ConstantPool) GetConstant(index uint) Constant {
if c := self.consts[index]; c != nil {
return c
}
panic(fmt.Sprintf("No constants at index %d", index))
}

newConstantPool()函数的switch循环体要稍微注意下:int和float分支直接去除常量值即可;long和double也是直接取出常量,但是由于这两种类型的常量在常量池中占据2个位置,所以索引要特殊处理下;字符串常量直接取字符串;剩下类、字段、方法和接口方法的符号引用需要单独处理,下面就来实现。

类符号引用

因为4种类型的符号引用有一些共性,所以仍然使用继承来减少重复代码,公用结构体位于cp_symref.go文件中:

1
2
3
4
5
6
7
8
9
// 类、字段、方法和接口方法的符号引用公共结构体
type SymRef struct {
// 运行时常量池指针,便于通过符号引用访问运行时常量池
cp *ConstantPool
// 类的完全限定名
className string
// 缓存解析后的类结构体指针
class *Class
}

类符号引用结构体位于cp_classref.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
// 类符号引用结构体
type ClassRef struct {
SymRef
}
// 初始化类符号引用结构体
func newClassRef(cp *ConstantPool, classInfo *classfile.ConstantClassInfo) *ClassRef {
ref := &ClassRef{}
ref.cp = cp
ref.className = classInfo.Name()
return ref
}

字段符号引用

定义MemberRef结构体来存放字段和方法符号引用公用信息,实现代码位于cp_memberref.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 字段和方法符号引用共用结构体
type MemberRef struct {
SymRef
// 字段或方法的名字
name string
// 字段或方法的描述符
descriptor string
}
// 从class文件中复制数据
func (self *MemberRef) copyMemberRefInfo(refInfo *classfile.ConstantMemberrefInfo) {
// 读取类名
self.className = refInfo.ClassName()
// 读取字段或方法的名字和描述符
self.name, self.descriptor = refInfo.NameAndDescriptor()
}
// 省略Getter方法
...

下面定义字段符号引用结构体,代码位于cp_fieldref.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字段符号引用结构体
type FieldRef struct {
MemberRef
// 字段信息结构体
field *Field
}
// 初始化字段符号引用
func newFieldRef(cp *ConstantPool, refInfo *classfile.ConstantFieldrefInfo) *FieldRef {
ref := &FieldRef{}
ref.cp = cp
ref.copyMemberRefInfo(&refInfo.ConstantMemberrefInfo)
return ref
}

方法符号引用

方法符号引用和字段符号引用类似,其实现代码位于cp_methodref.go文件中,比较简单,这里就不贴出来了。

接口方法符号引用

接口方法符号引用和字段符号引用类似,其实现代码位于cp_interface_methodref.go文件中,比较简单,这里就不贴出来了。

完善类信息

现在可以来完善下Class结构体,添加把ClassFile结构体换成Class结构体的方法,编辑class.go文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 将ClassFile结构体转换成Class结构体
func newClass(cf *classfile.ClassFile) *Class {
class := &Class{}
class.accessFlags = cf.AccessFlags()
class.name = cf.ClassName()
class.superClassName = cf.SuperClassName()
class.interfaceNames = cf.InterfaceNames()
class.constantPool = newConstantPool(class, cf.ConstantPool())
class.fields = newFields(class, cf.Fields())
class.methods = newMethods(class, cf.Methods())
return class
}
// 另外添加8个判断访问标志符的方法和Getter方法,参照项目源码
...

下节继续。。。