当前位置: 首页 > news >正文

第十章 用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的实现进度已经开始倒计时了。。。

http://www.jsqmd.com/news/785103/

相关文章:

  • Nuxt.js Auth模块与Laravel后端集成:JWT、Passport、Sanctum完整指南
  • CANN双三次上采样反向传播算子
  • 2026年AI Agent开发部署公司推荐指南:五大服务商多维度对比分析
  • 终极指南:Crypto-JS如何应对量子计算威胁?5个关键安全策略解析
  • 对比使用前后Taotoken在API调用延迟与稳定性上的体感差异
  • CANN/asc-devkit AddOutputTd函数
  • 浅析操作系统中的死锁问题及银行家算法
  • x265编码器深度解析:SAO模块技术详解
  • Linux配置IgH实现EtherCAT主战
  • 想选高性价比光伏支架?这些厂家值得你深入了解! - GrowthUME
  • 运维还在靠人肉排障?AI 运维时代已经开始“自己修自己”了
  • 构建AI编程助手操作系统:Codecast实现会话记忆与团队协作
  • 7步打造Taxonomy客户成功体系:从安装到用户支持的完整指南
  • 在taotoken控制台回顾历史用量高峰与api调用成功率的趋势
  • CANN元数据定义获取BlockDim
  • 深圳电子元器件代理企业排名:深智微科技领衔,4家同行各展所长
  • 北京靠谱宣传片拍摄公司推荐:行业分析+实用指南 - GrowthUME
  • 7个关键步骤:掌握Sanic代码审查的完整指南与最佳实践 [特殊字符]
  • React JSON Schema Form文件上传处理:终极完整指南 [特殊字符]
  • 昆明本地CPPM官方授权报名中心及联系方式 - 众智商学院课程中心
  • CANN/cannbot-skills迁移手册
  • AI与自动化如何重塑有机化学:从数据驱动到闭环实验
  • 终极前端性能清单:长期性能维护的完整指南
  • 革命性系统编程语言Rune:如何用Python语法编写比C++更快的安全代码
  • 终极指南:如何使用Deep-Research进行物联网设备连接与数据采集研究
  • 2026年必看:7款热门AI编程工具横评,Trae领跑
  • CVPR 2026 | 浙大阿里新框架:只看图片就能学会压缩Token!压缩率90%
  • claude code用户如何通过taotoken解决封号与token不足困扰
  • node-redis性能调优终极指南:内存使用、网络延迟、CPU占用优化
  • 构建编译型知识图谱:为AI智能体打造持久化记忆中枢