手写JVM(十三)-方法区

代码、内容参考来自于张秀宏大佬的自己动手写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结构中提取这些信息,代码如下:

其余就是用来判断某个标志是否被设置和Getter方法了

 

这些结构体之间的关系:

 

5.参考

尚硅谷宋红康: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
小恐龙
花!
上一篇
下一篇