bboyjing's blog

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

本章继续学习类和对象。

类加载器

Java虚拟机的类加载系统十分复杂,本节将初步实现一个简化版的类加载器,后面还会进行扩展。定义ClassLoader结构体,位于class_loader.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
type ClassLoader struct {
// 通过Classpath来搜索和读取class文件
cp *classpath.Classpath
// 记录已经加载的类数据,key是类完全限定名
classMap map[string]*Class
}
// 创建ClassLoader实例
func NewClassLoader(cp *classpath.Classpath) *ClassLoader {
return &ClassLoader{
cp: cp,
classMap: make(map[string]*Class),
}
}
// 把类数据加载到方法区
func (self *ClassLoader) LoadClass(name string) *Class {
// 判断类是否已经被加载
if class, ok := self.classMap[name]; ok {
// 已加载直接返回
return class
}
// 加载类
return self.loadNonArrayClass(name)
}
/*
数组类和普通类有很大的不同
它的数据并不是来自class文件,而是由Java虚拟机在运行期间生成
暂不考虑数组类的加载
*/
func (self *ClassLoader) loadNonArrayClass(name string) *Class {
// 找到class文件并把数据读到内存
data, entry := self.readClass(name)
// 解析class文件,生成虚拟机可以使用的类数据
class := self.defineClass(data)
// 链接
link(class)
fmt.Printf("[Loaded %s from %s]\n", name, entry)
return class
}

上面的readClass()、defineClass()和link()函数下面逐个来讲。

readClass()

readClass()方法只是调用了Classpath的ReadClass()方法,并进行了错误处理。

1
2
3
4
5
6
7
8
9
10
// 读取class
func (self *ClassLoader) readClass(name string) ([]byte, classpath.Entry) {
// 调用Classpath的ReadClass()方法
data, entry, err := self.cp.ReadClass(name)
if err != nil {
// 若出错则跑出ClassNotFoundException异常
panic("java.lang.ClassNotFoundException: " + name)
}
return data, entry
}

defineClass()

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
// 解析class
func (self *ClassLoader) defineClass(data []byte) *Class {
// 从byte数组中获取Class结构体
class := parseClass(data)
// 设置Class结构它的classloader指针
class.loader = self
resolveSuperClass(class)
resolveInterfaces(class)
self.classMap[class.name] = class
return class
}
// 把class文件数据转换成Class结构体
func parseClass(data []byte) *Class {
// 从byte数组中读取ClassFile
cf, err := classfile.Parse(data)
if err != nil {
//panic("java.lang.ClassFormatError")
panic(err)
}
// 返回class结构体
return newClass(cf)
}
// 解析超类符号引用
func resolveSuperClass(class *Class) {
// 除了java.lang.Object,否则要递归调用LoadClass()方法加载超类
if class.name != "java/lang/Object" {
class.superClass = class.loader.LoadClass(class.superClassName)
}
}
// 解析接口符号引用
func resolveInterfaces(class *Class) {
// 获取接口个数
interfaceCount := len(class.interfaceNames)
if interfaceCount > 0 {
class.interfaces = make([]*Class, interfaceCount)
for i, interfaceName := range class.interfaceNames {
class.interfaces[i] = class.loader.LoadClass(interfaceName)
}
}
}

类的链接分为验证和准备两个必要阶段:

1
2
3
4
5
6
7
8
9
func link(class *Class) {
verify(class)
prepare(class)
}
// 验证阶段
func verify(class *Class) {
// todo
}

为了确保安全性,Java虚拟机规范要求在执行类的任何代码之前,对类进行严格的验证,这里就不具体实现了,verify方法为空。准备阶段只要是给类变量分配空间并给予初始值,prepare()函数下面再讲。

对象、实例和类变量

之前我们定义过一个LocalVars结构体来表示局部变量表,这个结构体也正好可以用来表示类变量和实例变量。我们在/rtdata/heap下再新建一个slots.go文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Slot结构体
type Slot struct {
// 整数
num int32
// 引用
ref *Object
}
// Slot结构体数组
type Slots []Slot
// 新建Slot结构体数组
func newSlots(slotCount uint) Slots {
if slotCount > 0 {
return make([]Slot, slotCount)
}
return nil
}
// 省略Getter和Setter方法,参照项目源码

修改之前改过包的object.go文件,给Object结构体添加两个字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Object结构体
type Object struct {
// 对象的Class指针
class *Class
// 实例变量
fields Slots
}
func newObject(class *Class) *Object {
return &Object{
class: class,
fields: newSlots(class.instanceSlotCount),
}
}
// 省略Getter方法,参照项目源码

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

  1. 修改field.go文件,添加slotId字段以及若干方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 字段信息结构体
    type Field struct {
    // 字段和方法公用结构体
    ClassMember
    // 字段在slot中的位置
    slotId uint
    }
    // slotId Getter
    func (self *Field) SlotId() uint {
    return self.slotId
    }
    // 判断字段类型是否是long或double
    func (self *Field) isLongOrDouble() bool {
    return self.descriptor == "J" || self.descriptor == "D"
    }
  2. 修改class_loader.go文件,添加计算实例字段的个数,同时编号的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 计算实例字段的个数,同时编号
    func calcInstanceFieldSlotIds(class *Class) {
    // 初始化slotId为0
    slotId := uint(0)
    // 如果超类不为空,slotId从超类的字段个数后开始编号
    if class.superClass != nil {
    slotId = class.superClass.instanceSlotCount
    }
    for _, field := range class.fields {
    // 判断是否是实例字段
    if !field.IsStatic() {
    // 设置字段对应Slots数组中的位置
    field.slotId = slotId
    slotId++
    // 若字段类型是long或double,则占2个slot
    if field.isLongOrDouble() {
    slotId++
    }
    }
    }
    // 设置Class的实例字段个数
    class.instanceSlotCount = slotId
    }
  3. 修改class_loader.go文件,添加计算静态字段的个数,同时编号的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 计算静态字段的个数,同时编号
    func calcStaticFieldSlotIds(class *Class) {
    // 初始化slotId为0
    slotId := uint(0)
    for _, field := range class.fields {
    // 判断是否是静态变量
    if field.IsStatic() {
    // 设置字段对应Slots数组中的位置
    field.slotId = slotId
    slotId++
    // 若字段类型是long或double,则占2个slot
    if field.isLongOrDouble() {
    slotId++
    }
    }
    }
    // 设置Class的静态字段个数
    class.staticSlotCount = slotId
    }
  4. 静态变量赋值前的准备工作。在解析class文件章节的解析属性表中有讲过ConstantValue属性,该属性只会出现在field_info结构中,用于表示常量表达式的值,也就是保存了编译期已知的基本类型或String类型。下面要修改若干文件来实现这一功能:

    1. 修改field.go文件,添加constValueIndex

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // 字段信息结构体
      type Field struct {
      // 字段和方法公用结构体
      ClassMember
      // 字段在slot中的位置
      slotId uint
      // 常量表达式的值的索引
      constValueIndex uint
      }
      // constValueIndex Getter
      func (self *Field) ConstValueIndex() uint {
      return self.constValueIndex
      }
    2. 修改/classfile/member_info.go文件,添加获取ConstantValue属性的方法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 读取ConstantValue属性
      func (self *MemberInfo) ConstantValueAttribute() *ConstantValueAttribute {
      // 遍历field_info中的attributes
      for _, attrInfo := range self.attributes {
      switch attrInfo.(type) {
      case *ConstantValueAttribute:
      // 返回ConstantValueAttribute
      return attrInfo.(*ConstantValueAttribute)
      }
      }
      return nil
      }
    3. 修改field.go文件,添加从Classfile中读取constValueIndex的方法,并保存至Filed结构体中:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 从class文件中复制field_info的属性,此处只读取constValueIndex
      func (self *Field) copyAttributes(cfField *classfile.MemberInfo) {
      if valAttr := cfField.ConstantValueAttribute(); valAttr != nil {
      self.constValueIndex = uint(valAttr.ConstantValueIndex())
      }
      }
      // 初始化字段信息
      func newFields(class *Class, cfFields []*classfile.MemberInfo) []*Field {
      ...
      fields[i].copyAttributes(cfField)
      ...
      }
  5. 如果静态变量属于基本类型或String类型,有final修饰符,且它的值在编译期已知,则该值存储在运行时常量池中。修改class_loader.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
    // 初始化类静态私有变量
    func initStaticFinalVar(class *Class, field *Field) {
    // 获取Class的静态数组
    vars := class.staticVars
    // 获取运行时常量池
    cp := class.constantPool
    // 获取常量值索引(在常量池中第几个)
    cpIndex := field.ConstValueIndex()
    // 获取字段在静态数组中的位置
    slotId := field.SlotId()
    if cpIndex > 0 {
    /*
    根据字段描述符(字段类型)读取相应的值
    并将读取的常量值赋到Class静态数组对应的位置
    */
    switch field.Descriptor() {
    case "Z", "B", "C", "S", "I":
    val := cp.GetConstant(cpIndex).(int32)
    vars.SetInt(slotId, val)
    // 其他case省略,参照项目源码
    ...
    }
    }
    }
  6. 最后修改class_loader.go文件,完善prepare()方法:

    1
    2
    3
    4
    5
    6
    // 准备阶段
    func prepare(class *Class) {
    calcInstanceFieldSlotIds(class)
    calcStaticFieldSlotIds(class)
    allocAndInitStaticVars(class)
    }

下节继续。。。