手写JVM(十六)-类和对象

代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。

1.对象、实例变量和类变量

在\rtda\heap目录下创建slots.go文件,把slot.go和local_vars.go文件中的内容拷贝进来,然后在此基础上修改,代码如下:

package heap

import "math"

type Slot struct {
    num int32
    ref *Object
}

type Slots []Slot

func newSlots(slotCount uint) Slots {
    if slotCount > 0 {
        return make([]Slot, slotCount)
    }
    return nil
}

func (self Slots) SetInt(index uint, val int32) {
    self[index].num = val
}
func (self Slots) GetInt(index uint) int32 {
    return self[index].num
}

func (self Slots) SetFloat(index uint, val float32) {
    bits := math.Float32bits(val)
    self[index].num = int32(bits)
}
func (self Slots) GetFloat(index uint) float32 {
    bits := uint32(self[index].num)
    return math.Float32frombits(bits)
}

// long consumes two slots

func (self Slots) SetLong(index uint, val int64) {
    self[index].num = int32(val)
    self[index+1].num = int32(val >> 32)
}
func (self Slots) GetLong(index uint) int64 {
    low := uint32(self[index].num)
    high := uint32(self[index+1].num)
    return int64(high)<<32 | int64(low)
}

// double consumes two slots

func (self Slots) SetDouble(index uint, val float64) {
    bits := math.Float64bits(val)
    self.SetLong(index, int64(bits))
}
func (self Slots) GetDouble(index uint) float64 {
    bits := uint64(self.GetLong(index))
    return math.Float64frombits(bits)
}

func (self Slots) SetRef(index uint, ref *Object) {
    self[index].ref = ref
}
func (self Slots) GetRef(index uint) *Object {
    return self[index].ref
}

Slots结构体准备就绪,可以使用了。Class结构体早在6.1节就定义好了,代码如下:

打开\rtda\heap\object.go文件,给Object结构体添加两个字段,一个存放对象的Class指针,一个存放实例变量,代码如下:

package heap

type Object struct {
    class *Class
    fields Slots
}


// getters

func (self *Object) Class() *Class {
    return self.class
}
func (self *Object) Fields() Slots {
    return self.fields
}

接下来有两个问题要处理,一是如何知道静态变量和实例变量需要多少空间,另外一个是哪个字段对应Slots中的哪个位置。
第一个问题比较好解决,只要数一下类的字段即可。假设某个类有m个静态字段和n个实例字段,那么静态变量和实例变量所需的空间大小就分别是m’和n’。这里要注意两点。首先,类是可以继承的。也就是说,在数实例变量时,要递归地数超类的实例变量;其次,long和double字段都占据两个位置,所以m’>=m,n’>=n。第二个问题也不算难,在数字段时,给字段按顺序编上号就可以了。这里有三点需要要注意。首先,静态字段和实例字段要分开编号,否则会混乱。其次,对于实例字段,一定要从继承关系的最顶端,也就是java.lang.Object开始编号,否则也会混乱。最后,编号时也要考虑long和double类型。

打开field.go文件,给Field结构体加上slotId字段,代码如下:

type Field struct {
    ClassMember
    slotId uint
}

Field结构体的isLongOrDouble()方法返回字段是否是long或double类型,和加上Getter方法

func (self *Field) SlotId() uint {
    return self.slotId
}
func (self *Field) isLongOrDouble() bool {
    return self.descriptor == "J" || self.descriptor == "D"
}

 

在class_loader.go文件,在其中定义prepare()函数,代码如下:

// jvms 5.4.2
func prepare(class *Class) {
    calcInstanceFieldSlotIds(class)
    calcStaticFieldSlotIds(class)
    allocAndInitStaticVars(class)
}

 

calcInstanceFieldSlotIds()函数计算实例字段的个数,同时给它们编号,代码如下:

func calcInstanceFieldSlotIds(class *Class) {
    slotId := uint(0)
    if class.superClass != nil {
        slotId = class.superClass.instanceSlotCount
    }
    for _, field := range class.fields {
        if !field.IsStatic() {
            field.slotId = slotId
            slotId++
            if field.isLongOrDouble() {
                slotId++
            }
        }
    }
    class.instanceSlotCount = slotId
}

 

calcStaticFieldSlotIds()函数计算静态字段的个数,同时给它们编号,代码如下:

func calcStaticFieldSlotIds(class *Class) {
    slotId := uint(0)
    for _, field := range class.fields {
        if field.IsStatic() {
            field.slotId = slotId
            slotId++
            if field.isLongOrDouble() {
                slotId++
            }
        }
    }
    class.staticSlotCount = slotId
}

 

allocAndInitStaticVars()函数给类变量分配空间,然后给它们赋予初始值,代码如下:

func allocAndInitStaticVars(class *Class) {
    class.staticVars = newSlots(class.staticSlotCount)
    for _, field := range class.fields {
        if field.IsStatic() && field.IsFinal() {
            initStaticFinalVar(class, field)
        }
    }
}

 

因为Go语言会保证新创建的Slot结构体有默认值(num字段是0,ref字段是nil),而浮点数0编码之后和整数0相同,所以不用做任何操作就可以保证静态变量有默认初始值(数字类型是0,引用类型是null)。如果静态变量属于基本类型或String类型,有final修饰符,且它的值在编译期已知,则该值存储在class文件常量池中。

initStaticFinalVar()函数从常量池中加载常量值,然后给静态变量赋值,字符串常量将在第8章讨论,这里先调用panic()函数终止程序执行。代码如下:

func initStaticFinalVar(class *Class, field *Field) {
    vars := class.staticVars
    cp := class.constantPool
    cpIndex := field.ConstValueIndex()
    slotId := field.SlotId()

    if cpIndex > 0 {
        switch field.Descriptor() {
        case "Z", "B", "C", "S", "I":
            val := cp.GetConstant(cpIndex).(int32)
            vars.SetInt(slotId, val)
        case "J":
            val := cp.GetConstant(cpIndex).(int64)
            vars.SetLong(slotId, val)
        case "F":
            val := cp.GetConstant(cpIndex).(float32)
            vars.SetFloat(slotId, val)
        case "D":
            val := cp.GetConstant(cpIndex).(float64)
            vars.SetDouble(slotId, val)
        case "Ljava/lang/String;":
            panic("todo")
        }
    }
}

 

需要给Field结构体添加constValueIndex字段,修改newFields()方法,从字段属性表中读取constValueIndex,copyAttributes()方法的以及加上Getter方法代码如下:

type Field struct {
    ClassMember
    constValueIndex uint
    slotId uint
}

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)
        fields[i].copyAttributes(cfField)
    }
    return fields
}
func (self *Field) copyAttributes(cfField *classfile.MemberInfo) {
    if valAttr := cfField.ConstantValueAttribute(); valAttr != nil {
        self.constValueIndex = uint(valAttr.ConstantValueIndex())
    }
}
func (self *Field) ConstValueIndex() uint {
    return self.constValueIndex
}

 

MemberInfo结构体的ConstantValueIndex()方法在ch06\classfile\member_info.go文件中,代码如下:

func (self *MemberInfo) ConstantValueAttribute() *ConstantValueAttribute {
    for _, attrInfo := range self.attributes {
        switch attrInfo.(type) {
        case *ConstantValueAttribute:
            return attrInfo.(*ConstantValueAttribute)
        }
    }
    return nil
}

 

2.类符号引用解析

打开cp_symref.go文件,在其中定义ResolvedClass()方法,代码如下:

func (self *SymRef) ResolvedClass() *Class {
    if self.class == nil {
        self.resolveClassRef()
    }
    return self.class
}

 

如果类符号引用已经解析,ResolvedClass()方法直接返回类指针,否则调用resolveClassRef()方法进行解析。

Java虚拟机规范5.4.3.1节给出了类符号引用的解析步骤,

resolveClassRef()方法就是按照这个步骤编写的(有一些简化),代码如下:

// jvms8 5.4.3.1
func (self *SymRef) resolveClassRef() {
    // 获取当前Class指针
    d := self.cp.class
    // 通过需要引用的类的完全限定名加载类
    c := d.loader.LoadClass(self.className)
    // 判断d是否能有权限调用引用类c
    if !c.isAccessibleTo(d) {
        panic("java.lang.IllegalAccessError")
    }

    self.class = c
}

 

通俗地讲,如果类D通过符号引用N引用类C的话,要解析N,先用D的类加载器加载C,然后检查D是否有权限访问C,如果没有,则抛出IllegalAccessError异常。

Java虚拟机规范5.4.4节给出了类的访问控制规则,

把这个规则翻译成Class结构体的isAccessibleTo()方法,代码如下(在class.go文件中):

// jvms 5.4.4
func (self *Class) isAccessibleTo(other *Class) bool {
    return self.IsPublic() ||
    self.getPackageName() == other.getPackageName()
}

 

也就是说,如果类D想访问类C,需要满足两个条件之一:C是public,或者C和D在同一个运行时包内。第11章再讨论运行时包,这里先简单按照包名来检查。getPackageName()方法的代码如下(也在class.go文件中):

func (self *Class) getPackageName() string {
    if i := strings.LastIndex(self.name, "/"); i >= 0 {
        return self.name[:i]
    }
    return ""
}

比如类名是java/lang/Object,则它的包名就是java/lang。如果类定义在默认包中,它的包名是空字符串。

 

3.字段符号引用解析

打开cp_fieldref.go文件,在其中定义ResolvedField()方法,代码如下:

func (self *FieldRef) ResolvedField() *Field {
    if self.field == nil {
       self.resolveFieldRef()
    }
    return self.field
}

 

Java虚拟机规范5.4.3.2节给出了字段符号引用的解析步骤,

把它翻译成resolveFieldRef()方法,如果类D想通过字段符号引用访问类C的某个字段,首先要解析符号引用得到类C,然后根据字段名和描述符查找字段。如果字段查找失败,则虚拟机抛出NoSuchFieldError异常。如果查找成功,但D没有足够的权限访问该字段,则虚拟机抛出IllegalAccessError异常。

// jvms 5.4.3.2
func (self *FieldRef) resolveFieldRef() {
    d := self.cp.class
    // 解析字段符号引用之前需要先解析字段所属的类
    c := self.ResolvedClass()
    //根据字段名和描述符查找字段
    field := lookupField(c, self.name, self.descriptor)

    if field == nil {
        panic("java.lang.NoSuchFieldError")
    }
    if !field.isAccessibleTo(d) {
        panic("java.lang.IllegalAccessError")
    }

    self.field = field
}

 

字段查找步骤在lookupField()函数中,代码如下,首先在C的字段中查找。如果找不到,在C的直接接口递归应用这个查找过程。如果还找不到的话,在C的超类中递归应用这个查找过程。如果仍然找不到,则查找失败。

// 根据字段名和描述符查找字段
func lookupField(c *Class, name, descriptor string) *Field {
    for _, field := range c.fields {
        if field.name == name && field.descriptor == descriptor {
            return field
        }
    }

    for _, iface := range c.interfaces {
        if field := lookupField(iface, name, descriptor); field != nil {
            return field
        }
    }

    if c.superClass != nil {
        return lookupField(c.superClass, name, descriptor)
    }

    return nil
}

 

Java虚拟机规范5.4.4节也给出了字段的访问控制规则。这个规则同样也适用于方法,

 

所以把它(略做简化)实现成ClassMember结构体的isAccessibleTo()方法,代码如下(在class_member.go文件中):

// jvms 5.4.4
func (self *ClassMember) isAccessibleTo(d *Class) bool {
    if self.IsPublic() {
        return true
    }
    // 判断d是否能有权限调用引用类c
    c := self.class
    // 如果self字段是protected,只有子类和同一包下的类可以访问(isSubClassOf()函数后面给出)
    if self.IsProtected() {
        return d == c || d.isSubClassOf(c) ||
        c.getPackageName() == d.getPackageName()
    }
    // 如果是默认访问权限,则只有同一包下的类可以访问
    if !self.IsPrivate() {
        return c.getPackageName() == d.getPackageName()
    }
    // 如果是默认访问权限,则只有同一包下的类可以访问
    return d == c
}

 

用通俗的语言描述字段访问规则。如果字段是public,则任何类都可以访问。如果字段是protected,则只有子类和同一个包下的类可以访问。如果字段有默认访问权限(非public,非protected,也非privated),则只有同一个包下的类可以访问。否则,字段是private,只有声明这个字段的类才能访问。

 

4.参考

尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ

周志明:深入理解java虚拟机

张秀宏:自己动手写Java虚拟机 (Java核心技术系列)

GO语言官网:Standard library – Go Packages

Java虚拟机规范:Chapter 4. The class File Format (oracle.com)

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇