Java虚拟机规范对本地方法和本地方法栈的实现要求是非常宽松的,甚至明确说明“如果Java虚拟机不支持native方法,自己也不依赖传统栈(通常指“C Stacks”)的话,可以无需支持本地方法栈”。商业级别的HotSpot将虚拟机栈和本地方法栈合并,并采用JNI规范来实现本地方法。而本文也采用虚拟机栈和本地方法栈合并的模式来模仿编写。
很容易想到,要调用本地方法,可以采用一个Map来做映射:
|
|
暂时将registerNatives()设置为空实现。
本地方法的调用
本地方法调用还是在Java方法的架构下进行,可以是静态方法也可以是非静态方法,修改方法调用的公有逻辑MethodInvokeLogic(),将以前的钩子处理(用来忽略本地方法registerNatives,遇到该方法就将方法从虚拟机栈弹出)注释掉。
接着修改Method_的构造方法,思路无非就是如果这个方法是Native的,那么就填充一些参数(设定maxStack和maxLocals,注入code等),让原本的框架仍然“认为”它就是一个普通的方法,而在执行的时候真正去执行本地方法,执行完后,还要通过返回指令回到框架。
根据上面的思路,首先修改Method_构造方法,在末尾添加:
|
|
然后实现injectCodeAttribute方法:
|
|
这里用到了Java虚拟机预留的字节码指令0xfe,当虚拟机遇到该指令时,就会执行指令背后的相关逻辑,也就是如下代码:
|
|
最后要做的是在字节码指令工厂内对0xfe进行注册。
完成了上面的步骤,接下来只需要添加本地方法的实现就可以了。
如果想知道运行当前项目会调用几次registerNatives方法,可以在registerNatives的方法体添加打印语句,具体处理如下:
|
|
会打印出:
|
|
这两个类都拥有registerNatives方法,在static语句块内会先运行registerNatives(),输出打印语句。
不知不觉已经完成了一次本地方法的调用。
实现hashCode()
本地方法的实现都大同小异,这里列举如何实现Object类的hashCode方法。
首先进入Object.java的源代码有:
|
|
然后在OpenJDK里查询Object.c有:
|
|
按照这个结构仿写。
JObject & JSystem
这里的JObject和JSystem对应Object.c和System.c。JObject代码如下:
|
|
JNINativeMethod类用来存储参数,这些参数可以用来注册本地方法。而本地方法都声明在JVM_ENTRY接口中。
接下来需要修改Registry类,处理查找到registerNatives方法时的逻辑。前面已经提到了,在Java中Object和System都拥有registerNatives方法用来注册本地方法。当查找到registerNatives方法后,应该确定是哪一个类的registerNatives(),具体代码见下:
|
|
一些核心代码的理解见注释。同理可以修改JSystem.java中的代码。
完成JVM_IHashCode
接下来只要完成JVM_ENTRY下的JVM_IHashCode类,就能实现调用本地方法的功能,下面给出代码:
|
|
代码中没有自己去实现hashCode的计算,而是直接采用的虚拟机编写语言Java的API,这样就完成了本地方法调用。
小结
要完成更多本地方法的调用,就需要先行完成库的编写。本篇旨在了解基本原理,实现的过程中借用了0xfe
这个保留的字节码来触发执行本地方法的逻辑,也可以采用其它方式。而如果要探究商业级虚拟机本地方法调用的过程,可以参考相关技术规范。