bboyjing's blog

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

本节继续实现一些本地方法。

字符串拼接和String.intern()方法

在Java语言中,通过加号来拼接字符串。作为优化,javac编译器会把字符串拼接操作转换成StringBuilder,比如说下面这段代码:

1
2
3
4
String hello = "hello,";
String world = "world!";
String str = hello + world;
System.out.println(str);

很可能会被javac优化成下面这样:

1
2
String str = new StringBuilder().append("hello,").append("world!").toString();
System.out.println(str);

为了运行上面的代码,本节将实现以下3个本地方法:

  • System.arrayCopy()
  • Fload.floatToRawIntBits()
  • Double.doubleToRawLongBits()

可以跟踪下StringBuilder的append()、toString()方法,就可以找到上述方法的调用。

System.arrayCopy()方法

在/native/java/lang包下新建System.go文件,在其中注册arrayCopy()方法,实现代码没有什么特别的地方,就不贴出来了,参照项目源码。

Float.floatToRawIntBits()和Double.doubleToRawLongBits()方法

Float.floatToRawIntBits()和Double.doubleToRawLongBits()方法返回浮点数的编码,这两个方法大同小异而且也比较简单,代码在/native/java/lang包下,可自行参照。

String.intern()方法

之前已经实现了字符串池,但它只能在虚拟机内部使用,下面实现String类的intern()方法,让Java类库也可以使用它。实现代码位于/native/java/lang/String.go文件中,代码也比较简单,不贴了。

测试

在java项目中新建StringTest.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StringTest {
public static void main(String[] args) {
String s1 = "abc1";
String s2 = "abc1";
System.out.println(s1 == s2);
int x = 1;
String s3 = "abc" + x;
System.out.println(s1 == s3);
s3 = s3.intern();
System.out.println(s1 == s3);
}
}

测试:

1
2
3
4
5
6
7
cd /home/zhangjing/IdeaProjects/jvmgo/go
go install cn.didadu/jvmgo
./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.StringTest
// 输出如下
true
false
true

Object.hashCode()、equals()和toString()

Object类有3个非常重要的方法:hashCode()返回对象的哈希码;equals()用来比较两个对象是否相同;toString()返回对象的字符串表示。hashCode()是个本地方法,equals()和toString()则是用Java写的。下面实现hashCode方法,修改/native/java/lang/Object.go文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const jlObject = "java/lang/Object"
func init() {
...
native.Register(jlObject, "hashCode", "()I", hashCode)
}
// public native int hashCode();
// ()I
func hashCode(frame *rtdata.Frame) {
// 获取局部变量表第一个元素,this引用
this := frame.LocalVars().GetThis()
// 将对象引用转换成int32
hash := int32(uintptr(unsafe.Pointer(this)))
// 将计算后的hash code入栈
frame.OperandStack().PushInt(hash)
}

在java项目中新建ObjectTest.java用于测试,代码就不贴出来了,查看项目源码。下面时测试结果:

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.ObjectTest
// 输出如下
544632192
cn.didadu.ObjectTest@20766d80
false
true

Object.clone()

Object类提供clone()方法,用来支持对象克隆,这也是一个本地方法,修改/native/java/lang/Object.go文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func init() {
...
native.Register(jlObject, "clone", "()Ljava/lang/Object;", clone)
}
// protected native Object clone() throws CloneNotSupportedException;
// ()Ljava/lang/Object;
func clone(frame *rtdata.Frame) {
// 获取局部变量表第一个元素,this引用
this := frame.LocalVars().GetThis()
// 加载Cloneable类
cloneable := this.Class().Loader().LoadClass("java/lang/Cloneable")
if !this.Class().IsImplements(cloneable) {
panic("java.lang.CloneNotSupportedException")
}
// 将克隆后的对象引用入栈
frame.OperandStack().PushRef(this.Clone())
}

真正的Clone()逻辑在/rtdata/heap/object_clone.go文件中,代码比较长,就不贴出来了,自行查看项目源码。
在java项目中新建CloneTest.java用于测试,代码就不贴出来了,查看项目源码。下面时测试结果:

1
2
3
4
5
6
cd /home/zhangjing/IdeaProjects/jvmgo/go
go install cn.didadu/jvmgo
./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.CloneTest
// 输出如下
3.1415926
3.14

自动拆装箱

为了更好地融入Java的对象系统,每种基本类型都有一个包装类与之对应。从Java5开始,Java语法增加了自动装箱和拆箱能力,可以在必要时把基本类型转换成包装类型或者反之。这个增强完全是由编译器完成的,Java虚拟机没有做任何调整。
以int类型为例,它的包装类是java.lang.Integer。它提供了2个方法类帮助编译器在int变量和Integer对象之间转换:静态方法value()把int变量包装成Integer对象;实例方法intValue()返回被包装的int变量。查看Integer的源码可以看出,valueOf()方法并不是每次都创建Integer()对象,而是维护了一个缓存池IntegerCache。对于比较下(默认是-128 ~ 127)的int变量,在IntegerCache初始化时就预先加载到了池中,需要用时直接从池里取即可。再看IntegerCache类的实现,其中有一段sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");,调用了虚拟机初始化配置。下面我们先简单来实现下,在/native/sun/misc包下创建VM.go文件,注册initialize()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func init() {
native.Register("sun/misc/VM", "initialize", "()V", initialize)
}
// private static native void initialize();
// ()V
func initialize(frame *rtdata.Frame) {
vmClass := frame.Method().Class()
savedProps := vmClass.GetRefVar("savedProps", "Ljava/util/Properties;")
key := heap.JString(vmClass.Loader(), "foo")
val := heap.JString(vmClass.Loader(), "bar")
frame.OperandStack().PushRef(savedProps)
frame.OperandStack().PushRef(key)
frame.OperandStack().PushRef(val)
propsClass := vmClass.Loader().LoadClass("java/util/Properties")
setPropMethod := propsClass.GetInstanceMethod("setProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")
base.InvokeMethod(frame, setPropMethod)
}

上面只是一段hack代码,就不细看了,翻译成等价的Java代码就一句话:

1
2
3
private static navive void initialize() {
VM.savedProps.setProperty("foo", "bar")
}

最后在invokenative.go文件中导入misc包,就可以测试了,在java项目中新建BoxTest.java文件,代码就不贴出来了,下面给出测试结果:

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.BoxTest
// 输出如下
[1, 2, 3]
1
2
3

本地方法调用暂且就学到这里了。