手写JVM(二十七)-反射实现

代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。

1.类和对象之间的关系

在Java中,类也表现为普通的对象,它的类是java.lang.Class。类也是对象,而对象又是类的实例。

Java有强大的反射能力。可以在运行期间获取类的各种信息、存取静态和实例变量、调用方法,等等。要想运用这种能力,获取类对象是第一步。

在Java语言中,有两种方式可以获得类对象引用:使用类字面值和调用对象的getClass()方法。下面的Java代码演示了这两种方式。

System.out.println(String.class);
System.out.println("abc".getClass());

前面通过Object结构体的class字段建立了类和对象之间的单向关系。现在把这个关系补充完整,让它成为双向的。

打开\rtda\heap\class.go文件,修改Class结构体,添加jClass字段并定义Getter方法,改动如下:

通过jClass字段,每个Class结构体实例都与一个类对象关联。

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
    initStarted bool
    jClass *Object
}

对应Getter方法

func (self *Class) JClass() *Object {
    return self.jClass
}

 

下面打开\rtda\heap\object.go文件,修改Object结构体,添加extra字段,这个字段之所以是interface{}类型,是因为它还有其他用途。目前只用它来记录类对象对应的Class结构体指针。改动如下:

type Object struct {
    class *Class
    data interface{} // Slots for Object, []int32 for int[] ...
    extra interface{}
}

extra字段用来记录Object结构体实例的额外信息。同样给它定义Getter和Setter方法

func (self *Object) Extra() interface{} {
    return self.extra
}
//setter
func (self *Object) SetExtra(extra interface{}) {
    self.extra = extra
}

 

由于/rtdata/heap/object.go文件添加了extra属性,所以在array_class.go和string_pool.go文件中初始化Object结构体的时候要多加个参数,如图:

 

假设方法区中只加载了两个类,java.lang.Object和java.lang.Class;堆中只通过new指令分配了一个对象。此时Java虚拟机的内存状态如图所示:

图只画出了Class和Object结构体的必要字段,并且刻意分开了堆和方法区。在方法区中,class1和class2分别是java.lang.Object和java.lang.Class类的数据。在堆中,object1和object2分别是
java.lang.Object和java.lang.Class的类对象。object3是单独的java.lang.Object实例。

再看一张图;

 

2.修改类加载器

Class和Object结构体准备好了,接下来修改类加载器,让每一个加载到方法区中的类都有一个类对象与之相关联。

打开\rtda\heap\class_loader.go文件,修改NewClassLoader()函数,改动如下:

func NewClassLoader(cp *classpath.Classpath, verboseFlag bool) *ClassLoader {
    loader := &ClassLoader{
        cp: cp,
        verboseFlag: verboseFlag,
        classMap: make(map[string]*Class),
    }
    // 加载java.lang.Class类
    loader.loadBasicClasses()
    return loader
}

在返回ClassLoader结构体实例之前,先调用loadBasicClasses()函数。在class_loader.go文件中,加入loadBasicClasses()函数,loadBasicClasses()函数先加载java.lang.Class类,这又会触发java.lang.Object等类和接口的加载。然后遍历classMap,给已经加载的每一个类关联类对象。代码如下:

func (self *ClassLoader) loadBasicClasses() {
    // 加载java.lang.Class类,会触发java.lang.Object等类和接口的加载
    jlClassClass := self.LoadClass("java/lang/Class")
    // 遍历已经加载的每一个类
    for _, class := range self.classMap {
        // 给每个类设置关联类对象
        if class.jClass == nil {
            class.jClass = jlClassClass.NewObject()
            class.jClass.extra = class
        }
    }
}

 

下面修改LoadClass()方法,在类加载完之后,看java.lang.Class是否已经加载。如果是,则给类关联类对象。代码如下:

func (self *ClassLoader) LoadClass(name string) *Class {
    if class, ok := self.classMap[name]; ok {
        // already loaded
        return class
    }
    var class *Class
    // 若类名的第一个字符是'[',表示该类是数组类
    if name[0] == '[' {
        // array class
        class = self.loadArrayClass(name)
    } else {
        class = self.loadNonArrayClass(name)
    }
    // 如果已加载java.lang.Class类,则给类关联类对象
    if jlClassClass, ok := self.classMap["java/lang/Class"]; ok {
        class.jClass = jlClassClass.NewObject()
        class.jClass.extra = class
    }

    return class
}

这样,在loadBasicClasses()和LoadClass()方法的配合之下,所有加载到方法区的类都设置好了jClass字段。

 

3.基本类型的类

void和基本类型也有对应的类对象,但只能通过字面值来访问,如下面的Java代码所示。

举几个例子:

System.out.println(void.class);
System.out.println(boolean.class);
System.out.println(byte.class);

 

和数组类一样,基本类型的类也是由Java虚拟机在运行期间生成的。继续编辑class_loader.go文件,修改NewClassLoader()函数,增加loadPrimitiveClasses()方法加载void和基本类型的类,代码如下:

func NewClassLoader(cp *classpath.Classpath, verboseFlag bool) *ClassLoader {
    loader := &ClassLoader{
        cp: cp,
        verboseFlag: verboseFlag,
        classMap: make(map[string]*Class),
    }
    // 加载java.lang.Class类
    loader.loadBasicClasses()
    //加载void和基本类型的类
    loader.loadPrimitiveClasses()
    return loader
}

 

loadPrimitiveClasses()方法加载void和基本类型的类,生成void和基本类型类的代码在loadPrimitiveClass()方法中,代码如下:

func (self *ClassLoader) loadPrimitiveClasses() {
    for primitiveType, _ := range primitiveTypes {
        self.loadPrimitiveClass(primitiveType)
    }
}

func (self *ClassLoader) loadPrimitiveClass(className string) {
    class := &Class{
        accessFlags: ACC_PUBLIC, // todo
        name: className,
        loader: self,
        initStarted: true,
    }
    class.jClass = self.classMap["java/lang/Class"].NewObject()
    class.jClass.extra = class
    self.classMap[className] = class
}

 

这里有三点需要说明:

  • 第一,void和基本类型的类名就是void、int、float等。
  • 第二,基本类型的类没有超类,也没有实现任何接口。
  • 第三,非基本类型的类对象是通过ldc指令加载到操作数栈中的,基本类型的类对象,虽然在Java代码中看起来是通过字面量获取的,但是编译之后的指令并不是ldc,而是getstatic。每个基本类型都有一个包装类,包装类中有一个静态常量,叫作TYPE,其中存放的就是基本类型的类。也就是说,基本类型的类是通过getstatic指令访问相应包装类的TYPE字段加载到操作数栈中。

例如java.lang.Integer类。

 

4.修改ldc指令

和基本类型、字符串字面值一样,类对象字面值也是由ldc指令加载的。本节修改ldc指令,让它可以加载类对象。

打开\instructions\constants\ldc.go文件,修改_ldc()函数,增加了一个case语句,如果运行时,常量池中的常量是类引用,则解析类引用,然后把类的类对象推入操作数栈顶。改动如下:

// 支持类对象
case *heap.ClassRef:
    classRef := c.(*heap.ClassRef)
    classObj := classRef.ResolvedClass().JClass()
    stack.PushRef(classObj)

 

5.通过反射获取类名

为了支持通过反射获取类名,本小节将实现以下4个本地方法:

Object.getClass()返回对象的类对象引用。

java.lang.Object.getClass()

 

基本类型的包装类在初始化时会调用Class.getPrimitiveClass()给TPYE字段赋值。

java.lang.Class.getPrimitiveClass()

 

Character类是基本类型char的包装类,它在初始化时会调用Class.desiredAssertionStatus0()方法。

java.lang.Class.desiredAssertionStatus0()

 

要实现getName0()方法,是因为Class.getName()方法是依赖这个本地方法工作的,

java.lang.Class.getName0()

getName()方法的代码如下:

在\native\java\lang\Object.go文件,在其中注册getClass()本地方法,首先,从局部变量表中拿到this引用。有了this引用后,通过Class()方法拿到它的Class结构体指针,进而又通过JClass()方法拿到它的类对象。最后,把类对象推入操作数栈顶。代码如下:

package lang

import (
    "jvmgo/ch09/native"
    "jvmgo/ch09/rtda"
)

const jlObject = "java/lang/Object"

func init() {
    native.Register(jlObject, "getClass", "()Ljava/lang/Class;", getClass)
}

// public final native Class<?> getClass();
// ()Ljava/lang/Class;
func getClass(frame *rtda.Frame) {
    // 从局部变量表拿到this引用
    this := frame.LocalVars().GetThis()
    // 获取类对象
    class := this.Class().JClass()
    // 将类对象入栈
    frame.OperandStack().PushRef(class)
}

 

GetThis()方法在/rtda/local_vars.go文件中

func (self LocalVars) GetThis() *heap.Object {
    return self.GetRef(0)
}

在ch09/rtda/heap/class.go加上JavaName()方法,只是返回转换后的类名而已,下面需要使用。

func (self *Class) JavaName() string {
    return strings.Replace(self.name, "/", ".", -1)
}

 

在/rtda/operand_stack.go,加入将布尔值推入和弹出操作数栈顶的方法,下面需要使用。

func (self *OperandStack) PushBoolean(val bool) {
    if val {
        self.PushInt(1)
    } else {
        self.PushInt(0)
    }
}
func (self *OperandStack) PopBoolean() bool {
    return self.PopInt() == 1
}

 

在\native\java\lang目录下创建Class.go文件,在其中注册3个本地方法,代码如下:

package lang

import (
    "jvmgo/ch09/native"
    "jvmgo/ch09/rtda"
    "jvmgo/ch09/rtda/heap"
)

const jlClass = "java/lang/Class"

func init() {
    //注册3个本地方法
    native.Register(jlClass, "getPrimitiveClass", "(Ljava/lang/String;)Ljava/lang/Class;", getPrimitiveClass)
    native.Register(jlClass, "getName0", "()Ljava/lang/String;", getName0)
    native.Register(jlClass, "desiredAssertionStatus0", "(Ljava/lang/Class;)Z", desiredAssertionStatus0)
}

// static native Class<?> getPrimitiveClass(String name);
// (Ljava/lang/String;)Ljava/lang/Class;
func getPrimitiveClass(frame *rtda.Frame) {
    //先从局部变量表中拿到类名
    nameObj := frame.LocalVars().GetRef(0)
    //这是个Java字符串,需要把它转成Go字符串
    name := heap.GoString(nameObj)
    //调用类加载器的LoadClass()方法获取
    loader := frame.Method().Class().Loader()
    class := loader.LoadClass(name).JClass()
    //把类对象引用推入操作数栈顶
    frame.OperandStack().PushRef(class)
}

// private native String getName0();
// ()Ljava/lang/String;
func getName0(frame *rtda.Frame) {
    //从局部变量表中拿到this引用
    this := frame.LocalVars().GetThis()
    //通过Extra()方法可以获得与之对应的Class结构体指针
    class := this.Extra().(*heap.Class)
    //拿到类名
    name := class.JavaName()
    //转成Java字符串
    nameObj := heap.JString(class.Loader(), name)
    //推入操作数栈顶
    frame.OperandStack().PushRef(nameObj)
}

// private static native boolean desiredAssertionStatus0(Class<?> clazz);
// (Ljava/lang/Class;)Z
func desiredAssertionStatus0(frame *rtda.Frame) {
    // todo
    //把false推入操作数栈顶
    frame.OperandStack().PushBoolean(false)
}

 

解释一下上面代码:

getPrimitiveClass()是静态方法。先从局部变量表中拿到类名,这是个Java字符串,需要把它转成Go字符串。基本类型的类已经加载到了方法区中,直接调用类加载器的LoadClass()方法获取即可。最后,把类对象引用推入操作数栈顶。

 

下面实现getName0()方法,首先从局部变量表中拿到this引用,这是一个类对象引用,通过Extra()方法可以获得与之对应的Class结构体指针。然后拿到类名,转成Java字符串并推入操作数栈顶。注意这里需要的是java.lang.Object这样的类名,而非java/lang/Object。Class结构体的JavaName()方法返回转换后的类名

desiredAssertionStatus0()方法把false推入操作数栈顶

 

编辑\instructions\reserved\invokenative.go文件,在其中导入lang包,如果没有任何包依赖lang包,它就不会被编译进可执行文件,上面的本地方法也就不会被注册。所以需要一个地方导入lang包,把它放在invokenative.go文件中。由于没有显示使用lang中的变量或函数,所以必须在包名前面加上下划线,否则无法通过编译。

import (
    "jvmgo/ch09/instructions/base"
    "jvmgo/ch09/native"
    _ "jvmgo/ch09/native/java/lang"
    "jvmgo/ch09/rtda"
)
_操作其实就是引入该包。当导入一个包时,它所有的init()函数就会被执行,如果仅仅是希望它的init()函数被执行而已。这个时候就可以使用_操作引用该包了。即使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。

 

这个技术在Go语言中叫作“import for side effect”

Effective Go – The Go Programming Language

 

测试代码

用Java代码测试

package jvmgo.book.ch06;

public class GetClassTest {
    public static void main(String[] args) {
        System.out.println(void.class.getName()); // void
        System.out.println(boolean.class.getName()); // boolean
        System.out.println(byte.class.getName()); // byte
        System.out.println(char.class.getName()); // char
        System.out.println(short.class.getName()); // short
        System.out.println(int.class.getName()); // int
        System.out.println(long.class.getName()); // long
        System.out.println(float.class.getName()); // float
        System.out.println(double.class.getName()); // double
        System.out.println(Object.class.getName()); // java.lang.Object
        System.out.println(int[].class.getName()); // [I
        System.out.println(int[][].class.getName()); // [[I
        System.out.println(Object[].class.getName()); // [Ljava.lang.Object;
        System.out.println(Object[][].class.getName()); // [[Ljava.lang.Object;
        System.out.println(Runnable.class.getName()); // java.lang.Runnable
        System.out.println("abc".getClass().getName()); // java.lang.String
        System.out.println(new double[0].getClass().getName()); // [D
        System.out.println(new String[0].getClass().getName()); //[Ljava.lang.String;
    }
}

打开命令行窗口,执行下面的命令编译本章代码:

go install jvmgo\ch09
ch09 -classpath D:\MAT_log -Xjre "D:\software\java\jre" GetClassTest

 

6.参考

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