第十章 用Java实现JVM之本地方法调用

📅 2026/7/14 16:19:25 👁️ 阅读次数 📝 编程学习
第十章 用Java实现JVM之本地方法调用

用Java实现JVM目录

第零章 用Java实现JVM之随便说点什么
第一章 用Java实现JVM之JVM的准备知识
第二章 用Java实现JVM之命令行工具
第三章 用Java实现JVM之查找Class文件
第四章 用Java实现JVM之解析class文件
第五章 用Java实现JVM之运行时数据区
第六章 用Java实现JVM之指令集和解释器
第七章 用Java实现JVM之类和对象
第八章 用Java实现JVM之方法调用和返回
第九章 用Java实现JVM之数组和字符串
第十章 用Java实现JVM之本地方法调用
第十一章 用Java实现JVM之异常处理
第十二章 用Java实现JVM之结束


文章目录

  • 用Java实现JVM目录
  • 前言
  • 本地方法
  • 代码实现
      • 注册和查找本地方法
      • 调用本地方法
      • 实现本地方法
      • 初始化基础类型
  • 测试
  • 总结

前言

上一篇我们已经实现了字符串和数组,今天开启新的征程,继续往下。聚焦于本地方法

本地方法

提起“本地方法”这个词,可能很多人一时间会感到陌生:“这是什么?是指在本地调用的方法吗?”实际上,在Java的语境中,“本地方法”并不是我们日常写的普通方法,而是指由非 Java 语言(通常是 C/C++)编写的方法,然后通过JNI(Java Native Interface)的方式被Java所调用

那为什么Java需要调用C++写的方法?既然Java是跨平台的高级语言,为什么还要依赖底层语言来实现一些功能?难道Java自己不够强大吗?Java表示自己也很无奈啊。其实,这并不是Java的能力问题,而是它的边界问题。有些功能确实不是Java擅长或适合做的。比如:

  • 操作系统级别的功能Java本身是一种运行在虚拟机上的语言,和操作系统之间是隔着JVM的。但有些系统调用,比如访问硬件、操控内存地址、与驱动交互等,必须依赖原生语言,比如C/C++

  • 性能要求极高的场景C/C++在执行效率上天然比Java更靠近机器层面,比如要做一些高频率的图像处理、音视频编解码、数据库底层引擎等,使用本地方法可以显著提高性能

  • 复用已有的成熟库:很多早期开发的底层库、驱动程序甚至商业SDK都是用C/C++写的,直接复用比重新用Java实现一套更划算,也更稳定

因此,本地方法实际上是Java与底层系统打交道的一座桥梁,是Java向下“伸手”的一种方式。我们可以把它理解为一种“Java自己做不了,就让专业选手(C/C++)上”的机制。这也是为什么很多涉及底层优化、系统调用、硬件控制Java应用中,我们常常能看到native关键字的身影。它的存在就是为了告诉JVM:“这部分逻辑不是我写的,是由底层实现的,去调用对应的本地库吧。”

总之,本地方法不是 Java 的软肋,而是它在保持自身平台无关性的同时,灵活“借力”的一种表现

代码实现

注册和查找本地方法

扒拉了半天,道理我都懂,可是要从哪里开始啊,上面说的跟实现一点关系都没有。老样子,把自己当成JVM的设计师,在已知两种不同语言之间存在着相互调用,你会怎么做?

如果是两个不同语言之间互相调用,现在是Java调用C/C++,那么需要在JVM执行方法的时候识别并且注册本地方法。识别好说,本身就有native关键字。至于注册嘛!还是用Map结构,key就是方法签名,value就是具体的实现类。我们先定义一个接口,任何本地方法都必须实现这个接口。JNativeFunction代码如下:

/** * 执行本地方法接口 * * @author hqd */@FunctionalInterfacepublicinterfaceJNativeFunction{/** * 执行本地方法 * * @param jThread */voidinvoke(JThreadjThread);}

这个接口到时候肯定有很多实现,可不能自己一个个去new。多实现->策略->工厂。现在看到策略或者多实现,第一反应就是搭配工厂,用起来方便,省的一一去找,别人还得知道你有哪些实现。不过单纯的工厂不足以创建出来对应的本地方法实现,还不知道根据什么来创建,这里就取个巧,通过包名来。本地方法实现的包目录如下:


我们只需要获取本地方法所在类使用的包名,获取最后一个路径就能匹配上了。JNativeRegistryFactory代码如下:

/** * @author hqd */publicclassJNativeRegistryFactory{publicstaticJNativeFunctiongetInstance(JMethodjMethod){StringclassPath=jMethod.getJClass().getClassName();/** * 获取简单类名 */StringsimpleName=classPath.substring(classPath.lastIndexOf("/")+1);classPath=classPath.substring(0,classPath.lastIndexOf("/"));/** * 获取包名 */StringpackagePath=classPath.substring(classPath.lastIndexOf("/")+1);JNativeFunctionfunction=null;try{StringpackageName="com.hqd.jjvm.jnative."+packagePath+".";Class<JNativeRegistry>instanceClazz=(Class<JNativeRegistry>)Class.forName(packageName+"J"+simpleName+"NativeRegistry");instanceClazz.getConstructor().newInstance();function=JNativeRegistry.getJNativeFunction(jMethod);}catch(Exceptione){thrownewRuntimeException("get native function error:"+e.getMessage());}returnfunction;}}

而后在定义一个注册器,方便管理本地方法。JNativeRegistry代码如下:

/** * 注册本地方法 * @author hqd */publicclassJNativeRegistry{publicstaticfinalMap<String,JNativeFunction>JNATIVE_METHOD_MAP=newConcurrentHashMap<>();protectedstaticvoidregisterNatives(JThreadjThread){/** * TODO 啥也不干 */}publicstaticvoidregistry(JMethodmethod,JNativeFunctionfunction){registry(method.getJClass().getClassName(),method.getName(),method.getDescriptor(),function);}publicstaticJNativeFunctiongetJNativeFunction(JMethodmethod){Stringname=method.getJClass().getClassName()+"."+method.getName()+"."+method.getDescriptor();returnJNATIVE_METHOD_MAP.get(name);}}

调用本地方法

JVM在找到本地方法后,要怎么进行调用呢?既然是C/C++意味着没有方法体,不存在任何字节码。Java虚拟机规范也没有规定如何实现和调用本地方法,没规定?那就意味着可以随意发挥,可以充分的空间来发挥自己的想象力。于是,我们盯上了Java虚拟机规范预留了两条指令:

本地方法不是没有指令吗?那我们就给他指令。方法调用不是就是压栈吗?那就在压栈的时候插入我们自己的指令。我们先来实现指令。ReserveInstruction代码如下:

/** * @author hqd */publicclassReserveInstructionextendsAbstractInstruction{publicReserveInstruction(JThreadjThread){super(jThread);}@Overridepublicvoidexecute(InstructionTypeinstructionType){JMethodjMethod=getJMethod();JThreadjThread=getJThread();switch(instructionType){caseBREAKPOINT:{break;}caseIMPDEP1:{JNativeFunctionjNativeFunction=JNativeRegistryFactory.getInstance(jMethod);jNativeFunction.invoke(jThread);break;}caseIMPDEP2:{break;}}}}

再来先把之前的hack删掉,我们不需要他了。RefInstruction新增createNativeFrame方法,代码如下:

/** * 创建本地方法栈帧 * @param jThread * @param jMethod */privatevoidcreateNativeFrame(JThreadjThread,JMethodjMethod){jMethod.setMaxLocals(jMethod.getArgSlotCount());jMethod.setMaxStack(4);//创建指令StringBuildercode=newStringBuilder("FE");ClassUtil.getClassByDescriptor(jMethod.getDescriptor());switch(jMethod.getDescriptor().charAt(jMethod.getDescriptor().length()-1)){case'V':{// returncode.append("B1");break;}case'D':{// dreturncode.append("AF");break;}case'F':{// freturncode.append("AE");break;}case'J':{// lreturncode.append("AD");break;}case'C':case'S':case'B':case'Z':case'I':{// ireturncode.append("AC");break;}default:{// areturncode.append("B0");break;}}jMethod.setCode(newInstructionCode(code.toString()));jThread.createStackFrame(jMethod);}

而后,在把之前创建本地方法栈帧的TODO替换成对应方法:

实现本地方法

好了,准备工作已经做好了,接下来可以实现本地方法了。由于本地方法众多,这里就不一一展开说明。这里就是Object本地方法为例。我们使用的是Java语言,实现基本调用原本方法就行了。嘿嘿。JObjectNativeRegistry代码如下:

publicclassJObjectNativeRegistryextendsJNativeRegistry{privatestaticfinalJObjectNativeRegistryinstance=newJObjectNativeRegistry();protectedstaticfinalStringCLASS_PATH="java/lang/Object";static{registry(CLASS_PATH,"registerNatives","()V",JNativeRegistry::registerNatives);registry(CLASS_PATH,"hashCode","()I",JObjectNativeRegistry::hashCode0);registry(CLASS_PATH,"getClass","()Ljava/lang/Class;",JObjectNativeRegistry::getClass0);registry(CLASS_PATH,"clone","()Ljava/lang/Object;",JObjectNativeRegistry::clone0);registry(CLASS_PATH,"notifyAll","()V",JObjectNativeRegistry::notifyAll0);}privatestaticvoidnotifyAll0(JThreadjThread){}privatestaticvoidclone0(JThreadjThread){JObjectjObject=jThread.getJvmStack().getLocalVarsRefVal(0);try{JClassjc=jObject.getJClass().getLoader().loadJClass(Cloneable.class.getName());if(JClass.isImplements(jObject.getJClass(),jc)){thrownewCloneNotSupportedException(jObject.getJClass().getClassName()+" is not implements "+jc.getClassName());}jThread.getJvmStack().pushOperandStackRefVal(jObject.clone());}catch(Exceptione){thrownewRuntimeException("invoke clone0 error:"+e.getMessage());}}privatestaticvoidgetClass0(JThreadjThread){JObjectthisObj=jThread.getJvmStack().getTop().getLocalVars().getRefVal(0);jThread.getJvmStack().getTop().pushRef(thisObj.getJClass().getJObject());}protectedstaticvoidhashCode0(JThreadjThread){inthashCode=jThread.getJvmStack().getLocalVarsRefVal(0).getData().hashCode();jThread.getJvmStack().pushOperandStackIntVal(hashCode);}publicstaticJObjectNativeRegistrygetInstance(){returninstance;}}

初始化基础类型

现在我们已经可以处理本地方法了,其中难免遇到一些基础类型,还需要对基础类型进行加载。修改JClassLoader#initLoad()方法,使其加载基础类型,代码如下:

protectedvoidinitLoad(){try{JClassjClass=this.loadJClass(Class.class.getName());jClass.setJObject(JMethodArea.getJClass(Class.class.getName()).newJObject());jClass=this.loadJClass(Object.class.getName());jClass.setJObject(JMethodArea.getJClass(Class.class.getName()).newJObject());this.loadJClass(String.class.getName());loadPrimitiveClass("float");loadPrimitiveClass("int");loadPrimitiveClass("double");loadPrimitiveClass("long");loadPrimitiveClass("byte");loadPrimitiveClass("boolean");loadPrimitiveClass("char");loadPrimitiveClass("short");}catch(ClassNotFoundExceptione){thrownewRuntimeException(e);}}privateJClassloadPrimitiveClass(StringclassName){JClassjClass=newJClass();jClass.setAccessFlags(CommonAccessFlag.ACC_PUBLIC.getHex());jClass.setLoader(this);jClass.setState(JClassState.FULLY_INITIALIZED);jClass.setClassName(className);bindingJClassObj(jClass);JMethodArea.putJClass(jClass);returnJMethodArea.getJClass(className);}

测试

ok,今天的任务也完成了。接下来进入测试环节。定义StringTest类,代码如下:

publicclassStringTest{publicstaticvoidmain(String[]args){Strings1="abc1";Strings2="abc1";System.out.println(s1==s2);// trueintx=1;Strings3="abc"+x;System.out.println(s1==s3);// falses3=s3.intern();System.out.println(s1==s3);// true}}

再添加一个测试入口类,NativeTest代码如下:

publicclassNativeTest{publicstaticvoidmain(String[]args){CmdCommandcmdCommand=newCmdCommand();cmdCommand.parseCmd(args);}}

idea添加如下配置:

-Xjre"D:\Oracle\Java\jdk1.8.0_281\jre"-cp"Z:\code\jjvm\ch09\target\test-classes"com.hqd.jjvm.jnative.StringTest

测试结果如下:

两个运行结果一直,说明我们字符串池没有问题


总结

今天主要讲述本地方法调用,整个JVM的实现进度已经开始倒计时了。。。