代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。
在\rtda目录中创建heap子目录。
在rtda\object.go文件中定义了临时的Object结构体。现在可以把object.go移到heap目录下了。注意要修改包名,还需要修改slot.go、local_vars.go和operand_stack.go这三个文件,在其中添加heap包的import语句,并把*Object改成*heap.Object。
1.方法区概述
栈、堆、方法区的交互关系:


进入官网
https://docs.oracle.com/javase/specs/jvms/se8/html/index.html


方法区在哪里?:
《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但些简单的实现可能不会选择去进行垃圾收集或者进行压缩。” 但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
所以,方法区看作是一块独立于Java堆的内存空间。

方法区的基本理解
- 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
- 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
- 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutofMemoryError:PermGen space (JDK7)或者 java.lang.OutofMemoryError: Metaspace(JDK8)。

方法区(Method Area)存储什么?
《深入理解Java 虚拟机》书中对方法区(Method Area)存储内容描述如下它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

注意,类型信息指类、接口、枚举、域信息和方法信息之类的,即时编译器就是JIT。
类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
- 1. 这个类型的完整有效名称(全名=包名.类名)
- 2.这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类)
- 3. 这个类型的修饰符(public,abstract,final的某个子集)4这个类型直接接口的一个有序列表
- 4.这个类型直接接口的一个有序列表(如下图:)

域(Field)信息(属性,字段)
- 1.JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
- 2.域名称、域类型、域修饰符(public,private,域的相关信息包括:protected,static,final,volatile,transient的某个子集)
方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序 :
- 方法名称
- 方法的返回类型(或 void)
- 方法参数的数量和类型(按顺序)
- 方法的修饰符(public, private, protected,static, finalsynchronized,native, abstract的一个子集)
- 方法的字节码(bytecodes)、操作数栈及局部变量表的大小 (abstract和native方法除外)异常表(abstract和native方法除外) 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址被捕获的异常类的常量池索引。
方法区主要存放从class文件获取的类信息。类变量也存放在方法区中。当Java虚拟机第一次使用某个类时,它会搜索类路径,找到相应的class文件,然后读取并解析class文件,把相关信息放进方法区。注意方法区到底位于何处,是固定大小还是动态调整,是否参与垃圾回收,以及如何在方法区内存放类数据等,Java虚拟机规范是没有明确规定的。
2.类信息
类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
- 1. 这个类型的完整有效名称(全名=包名.类名)
- 2.这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类)
- 3. 这个类型的修饰符(public,abstract,final的某个子集)4这个类型直接接口的一个有序列表
- 4.这个类型直接接口的一个有序列表(如下图:)

全部代码
使用结构体来表示将要放进方法区内的类。
在\rtda\heap目录下创建class.go文件,全部代码如下,后面会解释:
package heap
import (
"jvmgo/ch06/classfile"
"strings"
)
// name, superClassName and interfaceNames are all binary names(jvms8-4.2.1)
type Class struct {
accessFlags uint16
name string // thisClassName
superClassName string
interfaceNames []string
constantPool *ConstantPool
fields []*Field
methods []*Method
loader *ClassLoader
superClass *Class
interfaces []*Class
instanceSlotCount uint
staticSlotCount uint
staticVars Slots
}
func newClass(cf *classfile.ClassFile) *Class {
class := &Class{}
class.accessFlags = cf.AccessFlags()
class.name = cf.ClassName()
class.superClassName = cf.SuperClassName()
class.interfaceNames = cf.InterfaceNames()
class.constantPool = newConstantPool(class, cf.ConstantPool())
class.fields = newFields(class, cf.Fields())
class.methods = newMethods(class, cf.Methods())
return class
}
func (self *Class) IsPublic() bool {
return 0 != self.accessFlags&ACC_PUBLIC
}
func (self *Class) IsFinal() bool {
return 0 != self.accessFlags&ACC_FINAL
}
func (self *Class) IsSuper() bool {
return 0 != self.accessFlags&ACC_SUPER
}
func (self *Class) IsInterface() bool {
return 0 != self.accessFlags&ACC_INTERFACE
}
func (self *Class) IsAbstract() bool {
return 0 != self.accessFlags&ACC_ABSTRACT
}
func (self *Class) IsSynthetic() bool {
return 0 != self.accessFlags&ACC_SYNTHETIC
}
func (self *Class) IsAnnotation() bool {
return 0 != self.accessFlags&ACC_ANNOTATION
}
func (self *Class) IsEnum() bool {
return 0 != self.accessFlags&ACC_ENUM
}
// getters
func (self *Class) ConstantPool() *ConstantPool {
return self.constantPool
}
func (self *Class) StaticVars() Slots {
return self.staticVars
}
在heap\access_flags.go文件中,代码如下:
package heap
const (
ACC_PUBLIC = 0x0001 // class field method
ACC_PRIVATE = 0x0002 // field method
ACC_PROTECTED = 0x0004 // field method
ACC_STATIC = 0x0008 // field method
ACC_FINAL = 0x0010 // class field method
ACC_SUPER = 0x0020 // class
ACC_SYNCHRONIZED = 0x0020 // method
ACC_VOLATILE = 0x0040 // field
ACC_BRIDGE = 0x0040 // method
ACC_TRANSIENT = 0x0080 // field
ACC_VARARGS = 0x0080 // method
ACC_NATIVE = 0x0100 // method
ACC_INTERFACE = 0x0200 // class
ACC_ABSTRACT = 0x0400 // class method
ACC_STRICT = 0x0800 // method
ACC_SYNTHETIC = 0x1000 // class field method
ACC_ANNOTATION = 0x2000 // class
ACC_ENUM = 0x4000 // class field
)
代码解释
结构体
accessFlags是类的访问标志,总共16比特。字段和方法也有访问标志,但具体标志位的含义可能有所不同。

比特位的含义
根据Java虚拟机规范,把各个比特位的含义统一定义在heap\access_flags.go文件中
如图:


Class结构体其余字段
在Class结构体。name、superClassName和interfaceNames字段分别存放类名、超类名和接口名。注意这些类名都是完全限定名,具有java/lang/Object的形式。constantPool字段存放运行时常量池指针,fields和methods字段分别存放字段表和方法表。Class结构体还有几个字段没有说明。loader字段存放类加载器指针,superClass和interfaces字段存放类的超类和接口指针,这三个字段将在6.3节介绍。staticSlotCount和instanceSlotCount字段分别存放类变量和实例变量占据的空间大小,staticVars字段存放静态变量,这三个字段将在6.4节介绍。

运行时常量池将在6.2节中详细介绍。
newClass函数
在class.go文件,在其中定义newClass()函数,用来把ClassFile结构体转换成Class结构体,newClass()函数又调用了newConstantPool()、newFields()和newMethods(),这三个函数的代码将在后面说。

判断某个访问标志是否被设置
在class.go文件,在其中定义8个方法,用来判断某个访问标志是否被设置。这8个方法都很简单。
再看这图:

value值的二进制都是最高有效位为1,其余位为0,value值的各种组合只能有一种情况,即二进制相加占据特定的位,所以进行按位与运算,就可以判断是否包含了此value值,所以可以用来判断某个访问标志是否被设置。
比如取数 X=1 0000 0000 0100 ,只需要与ACC_PUBLIC(二进制0100)进行按位与运算,不为0,就可以得到X的指定位为1,既包含ACC_PUBLIC(二进制0100)

Getter方法

3.字段信息
字段和方法都属于类的成员,它们有一些相同的信息(访问标志、名字、描述符)。为了避免重复代码,创建一个结构体存放这些信息。
全部代码
在\rtda\heap目录下创建class_member.go文件,在其中定义ClassMember结构体,代码如下:
package heap
import "jvmgo/ch06/classfile"
type ClassMember struct {
accessFlags uint16
name string
descriptor string
class *Class
}
func (self *ClassMember) copyMemberInfo(memberInfo *classfile.MemberInfo) {
self.accessFlags = memberInfo.AccessFlags()
self.name = memberInfo.Name()
self.descriptor = memberInfo.Descriptor()
}
func (self *ClassMember) IsPublic() bool {
return 0 != self.accessFlags&ACC_PUBLIC
}
func (self *ClassMember) IsPrivate() bool {
return 0 != self.accessFlags&ACC_PRIVATE
}
func (self *ClassMember) IsProtected() bool {
return 0 != self.accessFlags&ACC_PROTECTED
}
func (self *ClassMember) IsStatic() bool {
return 0 != self.accessFlags&ACC_STATIC
}
func (self *ClassMember) IsFinal() bool {
return 0 != self.accessFlags&ACC_FINAL
}
func (self *ClassMember) IsSynthetic() bool {
return 0 != self.accessFlags&ACC_SYNTHETIC
}
// getters
func (self *ClassMember) Name() string {
return self.name
}
func (self *ClassMember) Descriptor() string {
return self.descriptor
}
func (self *ClassMember) Class() *Class {
return self.class
}
class字段存放Class结构体指针,这样可以通过字段或方法访问到它所属的类。

copyMemberInfo()方法从class文件中复制数据,代码如下:

用来判断某个访问标志是否被设置。

Getter方法

在\rtda\heap目录下创建field.go文件,在其中定义Field结构体,代码如下:
package heap
import "jvmgo/ch06/classfile"
type Field struct {
ClassMember
}
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)
}
return fields
}
func (self *Field) IsVolatile() bool {
return 0 != self.accessFlags&ACC_VOLATILE
}
func (self *Field) IsTransient() bool {
return 0 != self.accessFlags&ACC_TRANSIENT
}
func (self *Field) IsEnum() bool {
return 0 != self.accessFlags&ACC_ENUM
}
在field.go文件,在其中定义Field结构体,Field都是从ClassMember中继承过来的。newFields()函数根据class文件的字段信息创建字段表。同时要加上判断某个标志是否被设置。

4.方法信息
方法比字段稍微复杂一些,因为方法中有字节码。
全部代码
在\rtda\heap目录下创建method.go文件,在其中定义Method结构体,代码如下:
package heap
import "jvmgo/ch06/classfile"
type Method struct {
ClassMember
maxStack uint
maxLocals uint
code []byte
}
func newMethods(class *Class, cfMethods []*classfile.MemberInfo) []*Method {
methods := make([]*Method, len(cfMethods))
for i, cfMethod := range cfMethods {
methods[i] = &Method{}
methods[i].class = class
methods[i].copyMemberInfo(cfMethod)
methods[i].copyAttributes(cfMethod)
}
return methods
}
func (self *Method) copyAttributes(cfMethod *classfile.MemberInfo) {
if codeAttr := cfMethod.CodeAttribute(); codeAttr != nil {
self.maxStack = codeAttr.MaxStack()
self.maxLocals = codeAttr.MaxLocals()
self.code = codeAttr.Code()
}
}
func (self *Method) IsSynchronized() bool {
return 0 != self.accessFlags&ACC_SYNCHRONIZED
}
func (self *Method) IsBridge() bool {
return 0 != self.accessFlags&ACC_BRIDGE
}
func (self *Method) IsVarargs() bool {
return 0 != self.accessFlags&ACC_VARARGS
}
func (self *Method) IsNative() bool {
return 0 != self.accessFlags&ACC_NATIVE
}
func (self *Method) IsAbstract() bool {
return 0 != self.accessFlags&ACC_ABSTRACT
}
func (self *Method) IsStrict() bool {
return 0 != self.accessFlags&ACC_STRICT
}
// getters
func (self *Method) MaxStack() uint {
return self.maxStack
}
func (self *Method) MaxLocals() uint {
return self.maxLocals
}
func (self *Method) Code() []byte {
return self.code
}
代码解释
maxStack和maxLocals字段分别存放操作数栈和局部变量表大小,这两个值是由Java编译器计算好的。code字段存放方法字节码。

newMethods()函数根据class文件中的方法信息创建Method表,代码如下:

maxStack、maxLocals和字节码在class文件中是以属性的形式存储在method_info结构中的。copyAttributes()方法从method_info结构中提取这些信息,代码如下:

这些结构体之间的关系:

5.参考
尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ
周志明:深入理解java虚拟机
张秀宏:自己动手写Java虚拟机 (Java核心技术系列)


