bboyjing's blog

自己动手写JVM三十【本地方法调用(二)】

反射

本节继续将反射相关内容

修改类加载器

Class和Object结构体准备好了,接下来修改类加载器,让每一个加载到方法区中的类都有一个类对象与之关联。修改/rtdata/heap/class_loader.go文件的NewClassLoader()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 创建ClassLoader实例
func NewClassLoader(cp *classpath.Classpath, verboseFlag bool) *ClassLoader {
loader := &ClassLoader{
cp: cp,
verboseFlag: verboseFlag,
classMap: make(map[string]*Class),
}
loader.loadBasicClasses()
// 基本类型的类的加载,下面会讲
loader.loadPrimitiveClasses()
return loader
}
// 加载java.lang.Class类
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()方法:

1
2
3
4
5
6
7
8
9
10
// 把类数据加载到方法区
func (self *ClassLoader) LoadClass(name string) *Class {
...
// 如果已加载java.lang.Class类,则给类关联类对象
if jlClassClass, ok := self.classMap["java/lang/Class"]; ok {
class.jClass = jlClassClass.NewObject()
class.jClass.extra = class
}
...
}

这样,在loadBasicClasses()和LoadClass()的配合下,所有加载到方法区的类都设置好了jClass字段,而且每一个类关联的类对象都是单独的。

基本类型的类

void和基本类型也有对应的类对象,但只能通过字面值来访问:

1
2
3
4
System.out.println(void.class);
System.out.println(boolean.class);
System.out.println(byte.class);
...

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 加载基本类型的类
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
}

这里有三点需要说明:

  1. void和基本类型的类名就是void、int、float等。
  2. 基本类型的类没有超类,也没有实现任何接口。
  3. 非基本类型的类对象时通过ldc指令加载到操作数栈中的,而基本类型的类对象虽然在Java代码中看起来是通过字面量获取的,但是编译之后的指令并不是ldc,而是getstatic。每个基本类型都有一个包装类,包装类中有一个静态常量,就做TYPE,其中存放的就是基本类型的类。也就是说,基本类型的类是通过getstatic指令访问相应包装类的TYPE字段加载到操作数栈中。
    1
    2
    //java.lang.Integer类中的TYPE常量
    public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

修改ldc指令

和基本类型、字符串字面值一样,类对象字面值也是由ldc指令加载的,修改/instructions/constants/ldc.go文件的_ldc()方法:

1
2
3
4
5
6
7
8
9
func _ldc(frame *rtdata.Frame, index uint) {
...
case *heap.ClassRef:
// 支持类对象
classRef := c.(*heap.ClassRef)
classObj := classRef.ResolvedClass().JClass()
stack.PushRef(classObj)
...
}

通过反射获取类名

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

  • java.lang.Object.getClass()
  • java.lang.Class.getPrimitiveClass()
  • java.lang.Class.getName0()
  • java.lang.Class.desiredAssertionStatus0()

新建/native/lang/Object.go文件,在其中注册getClass()本地方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func init() {
native.Register("java/lang/Object", "getClass", "()Ljava/lang/Class;", getClass)
}
/*
public final native Class<?> getClass();
()Ljava/lang/Class;
*/
func getClass(frame *rtdata.Frame) {
// 从局部变量表拿到this引用
this := frame.LocalVars().GetThis()
// 获取类对象
class := this.Class().JClass()
// 将类对象入栈
frame.OperandStack().PushRef(class)
}

GetThis()方法在/rtdata/local_vars.go文件中,代码比较简单,就不贴出来了,参照项目源码。
在/native/java/lang包下新建Class.go文件,在其中注册三个本地方法,代码就不贴出来了,参照项目源码。
到此,4个本地方法都实现好了,而且也在init()函数中注册,但还init()函数还没有机会执行,需要在invokenative.go文件中导入lang包,这属于Go语言范畴,这里就不讨论了。

1
import _ "cn.didadu/jvmgo/native/java/lang"

测试本节代码

在java项目中新建GetClassTest.java文件,用作测试:

1
2
3
4
5
6
7
8
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
...
}
}

还差一步,由于/rtdata/heap/object.go文件添加了extra属性,所以在array_class.go和string_pool.go文件中初始化Object结构体的时候要多加个参数,例如&Object{loader.LoadClass("[C"), chars, nil},最后给extra赋值nil就行了。
测试:

1
2
3
4
5
6
7
8
cd /home/zhangjing/IdeaProjects/jvmgo/go
go install cn.didadu/jvmgo
./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.GetClassTest
// 输出如下
void
boolean
byte
...