本章将实现线程共享的运行时数据区,包括方法去和运行时常量池。将进一步处理ClassFile结构体,把它加以转换,放进方法去以后供后续使用。还会初步讨论类和对象的设计,实现一个简单的类加载器,并且实现类和对象相关的部分指令。本章实现代码位于/rtdata/heap包下,先将之间定义的object.go文件移到heap包下,注意修改包名,以及使用到Object结构体的文件(slot.go、local_vars.go、operand_stack.go)。
方法区
方法区是运行时数据区的一块逻辑区域,由多个线程共享。方法区主要存放从class文件获取的类信息,此外,类变量也存放在方法区中。当Java虚拟机第一次使用某个类时,它会搜索类路径,找到相应的class文件,然后读取并解析class文件,把相关信息放进方法区。至于方法区到底位于何处,是固定大小还是动态调整,是否参与垃圾回收,以及如何在方法区内存放类数据等,Java虚拟机规范并没有明确规定。下面先来看看有哪些信息需要放进方法区。
类信息
使用结构体来表示要放进方法区内的信息,结构体位于class.go文件中:
我们逐个来看下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文件中:
字段信息结构体位于filed.go文件中:
方法信息
方法比字段稍微复杂一些,因为方法中有字节码,实现代码位于method.go文件中:
运行时常量池
运行时常量池主要存放两类信息:字面量和符号引用。字面量包括整数、浮点数和字符串字面量;符号引用包括类符号引用、字段符号引用、方法符号引用和接口方法符号引用。运行时常量池结构体位于constant_pool.go文件中:
newConstantPool()函数的switch循环体要稍微注意下:int和float分支直接去除常量值即可;long和double也是直接取出常量,但是由于这两种类型的常量在常量池中占据2个位置,所以索引要特殊处理下;字符串常量直接取字符串;剩下类、字段、方法和接口方法的符号引用需要单独处理,下面就来实现。
类符号引用
因为4种类型的符号引用有一些共性,所以仍然使用继承来减少重复代码,公用结构体位于cp_symref.go文件中:
类符号引用结构体位于cp_classref.go文件中:
字段符号引用
定义MemberRef结构体来存放字段和方法符号引用公用信息,实现代码位于cp_memberref.go文件中:
下面定义字段符号引用结构体,代码位于cp_fieldref.go文件中:
方法符号引用
方法符号引用和字段符号引用类似,其实现代码位于cp_methodref.go文件中,比较简单,这里就不贴出来了。
接口方法符号引用
接口方法符号引用和字段符号引用类似,其实现代码位于cp_interface_methodref.go文件中,比较简单,这里就不贴出来了。
完善类信息
现在可以来完善下Class结构体,添加把ClassFile结构体换成Class结构体的方法,编辑class.go文件:
下节继续。。。