bboyjing's blog

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

类和对象相关指令

本节继续来学习类和对象相关指令

putstatic和getstatic指令

putstatic指令给类的某个静态变量赋值,它需要两个操作数,第一个操作数是uint16索引,来自字节码。通过这个索引可以从当前类的运行时常量池中找到一个字段符号引用,解析这个字段符号引用就可以知道给类的哪个静态变量赋值。第二个操作数是要赋给静态变量的值,从操作数栈中弹出。该指令实现代码位于putstatic.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
41
42
43
44
45
46
//putstatic 指令结构体(2个字节操作数)
type PUT_STATIC struct{ base.Index16Instruction }
func (self *PUT_STATIC) Execute(frame *rtdata.Frame) {
// 获取当前正在执行的方法
currentMethod := frame.Method()
// 获取当前类
currentClass := currentMethod.Class()
// 获取运行时常量池
cp := currentClass.ConstantPool()
// 通过索引从常量池中获取字段符号引用
fieldRef := cp.GetConstant(self.Index).(*heap.FieldRef)
// 解析字段
field := fieldRef.ResolvedField()
class := field.Class()
// 判断字段是否是static
if !field.IsStatic() {
panic("java.lang.IncompatibleClassChangeError")
}
// 判断字段是否是final
if field.IsFinal() {
// 如果字段是final,则表示静态常量,只能在类初始化方法中赋值
if currentClass != class || currentMethod.Name() != "<clinit>" {
panic("java.lang.IllegalAccessError")
}
}
// 获取字段描述符,也就是字段的类型
descriptor := field.Descriptor()
// 获取字段在Slots中的索引
slotId := field.SlotId()
// 获取类的静态变量Slots
slots := class.StaticVars()
// 获取操作数栈
stack := frame.OperandStack()
// 通过字段的类型从操作数栈顶弹出相应的值,并给Class的静态变量Slots赋值
switch descriptor[0] {
case 'Z', 'B', 'C', 'S', 'I':
slots.SetInt(slotId, stack.PopInt())
// 省略其它case
...
default:
}
}

getstatic指令和putstatic指令正好相反,它取出类的某个静态变量值,然后推入操作数栈顶。其实现代码位于getstatic.go文件中,代码与putstatic类似,这里就不贴出来了,参照项目源码。

putfield和getfield指令

putfield指令给实例变量赋值,它需要三个操作数。前两个操作数是常量池索引和变量值,用法和putstatic差不多。第三个操作数时对象引用,从操作数栈中弹出。实现代码位于putfield.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
41
42
43
44
45
46
47
48
49
50
// putfield指令结构体(2个字节操作数)
type PUT_FIELD struct{ base.Index16Instruction }
func (self *PUT_FIELD) Execute(frame *rtdata.Frame) {
// 获取当前正在执行的方法
currentMethod := frame.Method()
// 获取当前类
currentClass := currentMethod.Class()
// 获取运行时常量池
cp := currentClass.ConstantPool()
// 通过索引从常量池中获取字段符号引用
fieldRef := cp.GetConstant(self.Index).(*heap.FieldRef)
field := fieldRef.ResolvedField()
// 判断字段是否是static
if field.IsStatic() {
panic("java.lang.IncompatibleClassChangeError")
}
// 判断字段是否是final
if field.IsFinal() {
// 如果字段是final,则表示静态常量,只能在类初始化方法中赋值
if currentClass != field.Class() || currentMethod.Name() != "<init>" {
panic("java.lang.IllegalAccessError")
}
}
// 获取字段描述符,也就是字段的类型
descriptor := field.Descriptor()
// 获取字段在Slots中的索引
slotId := field.SlotId()
// 获取操作数栈
stack := frame.OperandStack()
/*
根据字段类型从操作数栈弹出相应的变量,然后弹出对象引用
判断引用是否是null,若是抛出NullPointerException异常,否则通过引用实例变量赋值
*/
switch descriptor[0] {
case 'Z', 'B', 'C', 'S', 'I':
val := stack.PopInt()
ref := stack.PopRef()
if ref == nil {
panic("java.lang.NullPointerException")
}
ref.Fields().SetInt(slotId, val)
// 省略其它case
...
default:
}
}

getfield指令获取对象的实例变量值,然后推入操作数栈,它需要两个操作数。第一个操作数是uint16索引,用法和前面一样。第二个操作数是对象引用,和putfield指令类似,其实现代码位于getfield.go文件中,这里就不贴出来了,参照项目源码。

instanceof和checkcast指令

instanceof指令判断对象是否是某个类的实例(或对象的类是否实现了某个接口),并把结果推入操作数栈。instanceof指令需要两个操作数。第一个操作数是uint16索引,从方法的字节码中获取,通过这个索引可以从当前类的运行时常量池中找到一个类符号引用。第二个数是对象引用,从操作数栈中弹出。该实现代码位于instanceof.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
// instanceof指令结构体
type INSTANCE_OF struct{ base.Index16Instruction }
func (self *INSTANCE_OF) Execute(frame *rtdata.Frame) {
// 获取操作数栈
stack := frame.OperandStack()
// 弹出栈顶对象引用
ref := stack.PopRef()
// 如果对象引用为空,将0入栈
if ref == nil {
stack.PushInt(0)
return
}
// 获取运行时常量池
cp := frame.Method().Class().ConstantPool()
// 通过索引从常量池中获取类符号引用
classRef := cp.GetConstant(self.Index).(*heap.ClassRef)
// 解析类
class := classRef.ResolvedClass()
// 判断对象引用是不是class的实例(IsInstanceOf()函授后面给出)
if ref.IsInstanceOf(class) {
// 如果是,将1入栈
stack.PushInt(1)
} else {
// 如果不是,将0入栈
stack.PushInt(0)
}
}

checkcast指令和instanceof指令很像,区别在于:instanceof指令会改变操作数栈(弹出对象引用,入栈判断结果);checkcast则不改变操作数栈(如果判断失败,直接跑出异常),checkcast指令实现代码位于checkcast.go文件中,和instanceof指令类似,这里不贴出来了,参照项目源码。
下面来重点来实现IsInstanceOf()函数,修改object.go文件:

1
2
3
func (self *Object) IsInstanceOf(class *Class) bool {
return class.isAssignableFrom(self.class)
}

真正的逻辑在Class结构体的isAssignableFrom()函数中,这个方法稍微有些复杂,单独定义在/rtdata/heap/class_hierarchy.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 判断other Class是不是self Class的实例
func (self *Class) isAssignableFrom(other *Class) bool {
s, t := other, self
// other Class和self Class是同一类型
if s == t {
return true
}
if !t.IsInterface() {
// other Class是self Class的子类
return s.isSubClassOf(t)
} else {
// other Class实现了self Class接口
return s.isImplements(t)
}
}
// 省略isSubClassOf()和isImplements()函数,参照项目源码
...