本章继续学习类和对象。
类加载器
Java虚拟机的类加载系统十分复杂,本节将初步实现一个简化版的类加载器,后面还会进行扩展。定义ClassLoader结构体,位于class_loader.go文件下:
|
|
上面的readClass()、defineClass()和link()函数下面逐个来讲。
readClass()
readClass()方法只是调用了Classpath的ReadClass()方法,并进行了错误处理。
defineClass()
|
|
link()
类的链接分为验证和准备两个必要阶段:
为了确保安全性,Java虚拟机规范要求在执行类的任何代码之前,对类进行严格的验证,这里就不具体实现了,verify方法为空。准备阶段只要是给类变量分配空间并给予初始值,prepare()函数下面再讲。
对象、实例和类变量
之前我们定义过一个LocalVars结构体来表示局部变量表,这个结构体也正好可以用来表示类变量和实例变量。我们在/rtdata/heap下再新建一个slots.go文件:
修改之前改过包的object.go文件,给Object结构体添加两个字段:
接下来有两个问题要处理,一是如何知道静态变量和实例变量需要多少空间,另外一个是哪个字段对应Slots中的哪个位置。
第一个问题比较好解决,只要数一下类的字段即可。假设某个类有m个静态字段和哪个实例字段,那么静态变脸量和实例变量所需的空间大小就分别是m’和n’。这里需要注意两点,类时可以继承的,也就是说,在数实例变量时,要递归的数超类的实例变量;其次,long和double字段各占另个位置,所以m’>=m,n’>=n。
第二个问题也不算难,在数字时给字段按顺序编上编号就可以了。这里有三点需要注意:首先,静态字段和实例字段要分开编号,否则会混乱;其次,对于实例字段,一定要从继承关系的最顶端,也就是java.lang.Object开始编号,否则也会混乱;最后,编号时也要考虑long和double类型。
下面针对上述两个问题来进行编码,有如下几个步骤:
修改field.go文件,添加slotId字段以及若干方法:
1234567891011121314151617// 字段信息结构体type Field struct {// 字段和方法公用结构体ClassMember// 字段在slot中的位置slotId uint}// slotId Getterfunc (self *Field) SlotId() uint {return self.slotId}// 判断字段类型是否是long或doublefunc (self *Field) isLongOrDouble() bool {return self.descriptor == "J" || self.descriptor == "D"}修改class_loader.go文件,添加计算实例字段的个数,同时编号的函数:
1234567891011121314151617181920212223// 计算实例字段的个数,同时编号func calcInstanceFieldSlotIds(class *Class) {// 初始化slotId为0slotId := uint(0)// 如果超类不为空,slotId从超类的字段个数后开始编号if class.superClass != nil {slotId = class.superClass.instanceSlotCount}for _, field := range class.fields {// 判断是否是实例字段if !field.IsStatic() {// 设置字段对应Slots数组中的位置field.slotId = slotIdslotId++// 若字段类型是long或double,则占2个slotif field.isLongOrDouble() {slotId++}}}// 设置Class的实例字段个数class.instanceSlotCount = slotId}修改class_loader.go文件,添加计算静态字段的个数,同时编号的函数:
12345678910111213141516171819// 计算静态字段的个数,同时编号func calcStaticFieldSlotIds(class *Class) {// 初始化slotId为0slotId := uint(0)for _, field := range class.fields {// 判断是否是静态变量if field.IsStatic() {// 设置字段对应Slots数组中的位置field.slotId = slotIdslotId++// 若字段类型是long或double,则占2个slotif field.isLongOrDouble() {slotId++}}}// 设置Class的静态字段个数class.staticSlotCount = slotId}静态变量赋值前的准备工作。在解析class文件章节的解析属性表中有讲过ConstantValue属性,该属性只会出现在field_info结构中,用于表示常量表达式的值,也就是保存了编译期已知的基本类型或String类型。下面要修改若干文件来实现这一功能:
修改field.go文件,添加constValueIndex
1234567891011121314// 字段信息结构体type Field struct {// 字段和方法公用结构体ClassMember// 字段在slot中的位置slotId uint// 常量表达式的值的索引constValueIndex uint}// constValueIndex Getterfunc (self *Field) ConstValueIndex() uint {return self.constValueIndex}修改/classfile/member_info.go文件,添加获取ConstantValue属性的方法:
123456789101112// 读取ConstantValue属性func (self *MemberInfo) ConstantValueAttribute() *ConstantValueAttribute {// 遍历field_info中的attributesfor _, attrInfo := range self.attributes {switch attrInfo.(type) {case *ConstantValueAttribute:// 返回ConstantValueAttributereturn attrInfo.(*ConstantValueAttribute)}}return nil}修改field.go文件,添加从Classfile中读取constValueIndex的方法,并保存至Filed结构体中:
12345678910111213// 从class文件中复制field_info的属性,此处只读取constValueIndexfunc (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)...}
如果静态变量属于基本类型或String类型,有final修饰符,且它的值在编译期已知,则该值存储在运行时常量池中。修改class_loader.go文件,添加给静态变量分配空间,同时赋予初始值的函数:
12345678910111213141516171819202122232425// 初始化类静态私有变量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省略,参照项目源码...}}}最后修改class_loader.go文件,完善prepare()方法:
123456// 准备阶段func prepare(class *Class) {calcInstanceFieldSlotIds(class)calcStaticFieldSlotIds(class)allocAndInitStaticVars(class)}
下节继续。。。