FFM JNI
2026/8/30 7:19:03 网站建设 项目流程

一、JAVA调用上位机的动态链接库.dll的方式

JNIFFM
Java Native Interface(Java 本地接口)Foreign Function & Memory API(外来函数内存接口)
编写C胶水代码纯JAVA代码

问题:MinGW运行时DLL:缺少DLL依赖。运行时可能会提示缺少libgcc_s_seh-1.dlllibstdc++-6.dlllibwinpthread-1.dll等文件,导致程序在其他电脑上无法启动。

解决:

1.静态链接

2.分发dll

1.1 FFM:必须要求 C++ 导出 extern "C" 函数
FFM目标:让Java直接调用extern "C"导出的函数。

为什么你用 FFM 调用 DLL 时,必须要求 C++ 导出 extern "C" 函数:
FFM 按"原始函数名"找函数,C++ 默认会把名字"加密",extern "C"就是告诉编译器"别加密"。

extern "C" 是 C++ 的语言链接规范,在编译阶段告诉编译器以 C 语言的方式处理函数名和调用约定, 避免 C++ 的 name mangling (名称修饰),确保JavaFFM能通过原始函数名找到对应的入口点。

1.C++支持函数重载(同名函数不同参数),为了区分这些同名函数,C++ 编译器做了名称修饰 → 导出的符号名 ≠ 你写的函数名

2.FFM 按字面名字精确查找 → 找不到被修饰后的符号

3.extern "C" 关闭名称修饰 → 符号名恢复为原始函数名,FFM 就能找到了
加了extern "C"之后,C 语言不支持函数重载,同一个编译单元里不能存在两个同名函数



1.2FFM 中的“C 函数签名”
C 函数签名(Function Signature)
,简单来说就是一个函数的“完整身份标识”。它告诉编译器和调用者:这个函数叫什么名字、需要传入什么参数、会返回什么类型的数据
FFM 中的“C 函数签名” = 函数名 + 返回值类型 + 参数类型列表,由FunctionDescriptor精确描述,是 Java 与 C 之间安全通信的“协议说明书”。

JNI签名:
JNI 签名是一种 标准化的字符串格式 ,用于在 JNI 中唯一标识 Java 方法。因为Java 支持方法重载(同名不同参数),必须通过签名来区分。
签名结构:(参数类型签名)返回值类型签名

  • C胶水代码:JAVA与C++之间并不兼容,为了连接JAVA与C++库必写的中间适配代码。
  • 什么是JNI胶水代码?举个例子说明。
    在C++端编写,充当Java与C++之间交互桥梁的这部分代码。
    实现Java和C++的跨语言通信,Java运行在JVM(Java虚拟机,JVM不直接运行源代码,而是通过编译生成字节码,JVM只运行字节码)中,C++直接编译成机器码,运行在操作系统层面。它们的执行环境完全隔离,数据类型互不兼容,无法直接进行通信。胶水代码通过JNI定义的一组标准接口来“粘合”,实现跨语言调用。
  • 确定性释放:指内存或资源在明确可知的、可预测的时间点被释放,而不是依赖垃圾回收器在未来的某个不确定时刻回收。
  • JVM:Java Virtual Machine(JAVA虚拟机)
  • JVM与JIT
    javac编译时生成.class字节码,但是这个字节码是JVM的专属虚拟指令,并不是物理计算机能看懂的二进制文件,JVM 默认先用解释器边翻译边执行字节码,而 JIT 即时编译器会监控重复执行的热点代码,提前将其编译为当前硬件可识别的本地机器码并缓存,后续再次执行时直接调用缓存机器码,无需重复翻译,提升运行性能。



    1.Java 代码不直接跑在操作系统上,先编译成.class字节码(JVM的“机器码”-二进制文件,但是不是CPU-物理计算机能看懂的二进制);
    2.JVM 是一层虚拟中间层,负责把字节码翻译成当前系统能执行的指令;
    3.实现跨平台:一次编译,到处运行Windows/Linux/Mac 各有对应版本 JVM,同一套 class 文件全都能跑
    4.JIT是JVM的一部分。
    JIT:JVM 监控代码,发现高频重复执行的代码,直接一次性编译成本地机器码缓存起来,后续调用直接跑机器码,速度大幅提升。
    5.解释器:JVM 用解释器逐条翻译字节码,翻译成当前操作系统 CPU 能识别的本地机器码,执行完就丢,不保存。
    优缺点:启动速度快;频繁调用的方法反复翻译,性能差。
  • Java 调用 JNI 函数时,每次跨越 Java → C边界都有固定成本,JIT 编译器无法穿越这个边界进行优化。

二、JDK IDEA安装
IDEA安装教程配置java环境(超详细)_idea配置java,零基础入门到精通,收藏这篇就够了_idea安装教程及环境配置-CSDN博客

三、IDEA下的main

  • src 下的 Main(Main.java):原代码文件--手动编写的,未编译,不能直接被JVM运行

  • out/production/FMM_dll 下的 Main(Main.class):文件后缀名.class,编译后的字节码文件
    JVM真正执行的文件,Java虚拟机只能识别class字节码

修改并运行src/Main.java时,IDEA 会自动触发编译,覆盖更新out/production/FMM_dll/Main.class

三.C++ DLL动态链接库文件

  1. tcp_comm_app与tcp_comm_framework
  2. 生成动态链接库


  3. C++核心共享库与C桥接共享库
    C桥接层共享库依赖C++共享库




    tcp_comm_c_api.h 文件中的声明 就是 Java FFM 调用时需要的函数签名 。
  4. C++ DLL的函数签名--当前DLL要有可直接被Java FFM调用的函数签名
    问题:Java FFM 只能调用 extern "C" 的纯 C 风格平坦函数 ,无法直接调用 C++ 类
    解决:创建C桥接层,用opaque handle 模式将 C++ 类包装成纯 C 函数接口

    opaque handle :不透明句柄外部使用者只能拿到句柄标识符,看不到、无法直接访问底层内部数据结构,所有操作必须通过库 / 模块提供的公开 API 完成。
    只能拿句柄提供的API接口去使用它,但是你看不到它的内部结构;
    一种强封装、信息隐藏的接口设计范式,C 语言生态最常用。






    句柄对应的C++对象存放在堆区:


    .dll与.dll.a:

四、Java 调用

  • JDK 21 --创建Maven配置文件pom.xml,配置JDK 21 和 -enable-preview 参数
    IDEA右下角显示 "Maven 'FMM_dll' build scripts found",说明IDEA还没有加载Maven项目配置。我需要让IDEA识别Maven项目。

  • pom.xml文件

    pom.xmlMaven 项目的核心配置文件(Project Object Model,项目对象模型),放在项目根目录,Maven 依靠这个文件管理项目所有行为:编译、JDK 版本、依赖、打包、构建命令等。

    代码说明:
    作用:这份pom.xml是你fmm-dll项目的构建规则文件,核心作用是告诉 Maven 使用 Java25 标准编译源码,适配 FFM 调用 DLL 的业务场景,统一管控项目编译环境、编码与基础信息。
    1.项目基础坐标(唯一标识)
    2.统一JDK25配置

    3.build 构建配置:手动指定整个项目Java源码的顶层根目录。

    4.编译插件--核心

  • run.bat文件
    Windows 下的批处理启动脚本,一键完成「清理→编译→运行」你的 Java FFM DLL 项目,不用手动在 IDEA 点运行,双击就能执行。

  • 调试时自动生成.xml文件(不用学,简单看)
    GrepConsole.xml--IDEA 内置编译器配置,告诉IDEA怎么编译你的Java代码。
    encodings.xml--记录项目所有文件的编码(你项目统一 UTF-8),解决中文乱码。
    GrepConsole--插件配置,保存控制台日志的过滤规则和高亮配置
    jarRepositories.xml--记录项目中使用的 Maven 远程仓库地址
    misc.xml--杂项配置:项目 SDK 版本、输出路径、全局环境参数。
    workspace.xml--工作量最大的本地配置:打开了哪些文件、断点、Run/Debug 运行配置、窗口布局、光标位置、插件开关。

五、FFM实现

  1. JDK25
  2. C++下:C桥接共享库
    tcp_comm_c_api.h 文件中的声明 就是 Java FFM 调用时需要的函数签名 。
    C++的代码:C++封装层,把底层C++通信框架暴露为标准C接口,允许Java调用该TCP通信库
  3. Java下:创建.java--Java FFM绑定类,封装所有C API调用
    这是一个 Java 通过 FFM (Foreign Function & Memory) API 调用本地 C++ DLL 进行 TCP 通信 的测试项目。

六、JNI实现

1.问题:

问题1:胶水代码.cpp为什么每个JNI函数都要加锁?

  • 多余代码,JAVA层是多线程需要加锁,但是现在JAVA内是单线程,不需要加锁。
  • Java是单线程,JNI函数串行调用,不存在多个JNI函数同时访问g_jni_iocontext的情况,不需要锁;
  • 后台线程( control_thread_ 、 data_thread_ 、 thread_pool_ )只执行 io_context::run() ,不修改 g_jni_context 中的智能指针,不需要锁;
  • AppController 内部已经有 receive_mutex_ 保护消息队列,不需要锁;
  • shuntdown销毁资源顺序:先stop停止IO,再join等待线程退出,最后reset安全。操作顺序正确,不需要锁

问题2:防止重复初始化方法

方法优点缺点适用场景
原子标志无锁开销,高效需要 C++11+单线程/多线程都适用
互斥锁+标志线程安全,简单有锁开销多线程场景
RAII模式自动管理,无需手动检查需要重构代码结构新项目推荐
检查智能指针简单,无需新增成员不够明确,可能误判临时方案

  • 注意:RAII(Resource Acquisition Is Initialization):资源获取即初始化
  • 资源的申请放在类的构造函数, 把资源的释放放在类的析构函数。 只要对象生命周期结束(出作用域、函数 return、异常退出),析构自动执行,资源一定会释放,杜绝泄漏。

    资源泛指所有需要手动释放的东西。

问题3:Lambda 捕获问题[](){}

  • 全局变量不会有生命周期问题

  • [&]引用捕获缺点:
  • [&]隐式捕获所有变量,不清楚捕获了什么
  • 若将g_jni_context改为局部变量,线程执行时变量可能已经销毁,会导致未定义行为

2.JNI回调--通过JNI将回调注册到Java层

JavaVM*:JavaVM指针。指向Java虚拟机实例。

  • JavaVM*就是 Java 虚拟机实例在 JNI(C/C++)层面的"指针"或"句柄"。

  • 不需要关系具体指向,只需要知道它是JVM的入口钥匙。
  • Java进程生命周期中,JavaVM*是单例的(只有一个),不管在多少C++线程中调用它,拿到的都是同一个指针。

Java虚拟机实例:本质上就是用java命令启动一个程序时,在操作系统内存中创建的正在运行的JVM进程本身。承载着你所有 Java 代码运行的“容器”或“操作系统”。(把Java 虚拟机实例想象成一个大型工厂

3.JavaVM*vsJNIEnv*(对比)

举例:把Java 虚拟机实例想象成一个大型工厂

  • 工厂本身(JavaVM*:负责整个工厂的运营管理。它知道所有车间(线程)的位置,能调配资源,也能随时查看工厂的整体状态。

  • 车间主任(JNIEnv*:每个车间主任只负责自己的车间(线程)。他不能去别的车间发号施令。如果你想在 C++ 的某个新线程里干活,必须通过工厂(JavaVM*)派一个该车间的主任(AttachCurrentThread获取JNIEnv*)给你。

4.Java文件

5、JNI跨语言通信

字符串转换 :Java 和 C++ 运行在不同环境,必须通过 JNI API 进行转换才能互相访问数据

将 Java 字符串转换为 C++ 字符串:是为了java向C++传数据,C++识别不出Java字符串,所以用jni转为C++字符串

C++ 字符串转换为 Java 字符串:是为了C++返回结果给Java和JNI回调,Java字符串识别不出C++字符串

七、FFM(Foreign Function & Memory API)与JNI

1.FFM是JNI的替代方案

与 JNI 相比,FFM 带来的核心好处主要体现为开发效率和安全性的大幅提升:
纯Java开发模型,告别复杂胶水代码
:JNI需要开发者编写繁琐的C/C++桥接代码和头文件,维护多套工具链,工作量大且容易出错。而FFM API允许开发者在纯Java代码中直接描述和调用本地函数、操作本地内存,省去了所有中间步骤。
更高的安全性:JNI中,一个错误的指针操作就可能导致JVM崩溃。FFM API则对内存的分配、访问和释放提供了更严格的类型检查和生命周期管理(通过`MemorySession`),能有效预防悬垂指针、内存泄漏等常见且危险的错误。
更标准、规范的API:JNI因其复杂性,催生了JNA、JNR等第三方框架来解决部分问题,但并未成为标准。FFM API是官方标准库的一部分,拥有统一的编程模型。

2. 替代后,使用FFM是提高调用效率还是语言切换效率?

两者都有提升,但角度和场景不同。总的来说,FFM 提高了语言切换(跨边界调用)的效率,且在长期、高频调用的场景下,整体调用效率也更高。

语言切换效率(跨边界调用的“过路费”):这是FFM优化最显著的地方。JNI的方法调用无法被JIT(即时编译器)内联等优化,性能开销巨大。而FFM API构建在`MethodHandle`(方法句柄)之上,这是一种为高效动态调用设计的VM(虚拟机)机制。这使得JIT编译器可以更好地优化跨边界调用,**减少了Java与本地代码间上下文切换和状态转换的开销**。一份Apache Artemis项目的改进计划就指出,FFM相比JNI有更低的开销(Lower overhead)。
调用效率(从“启动”到“长期运行”的表现):这里的情况更有趣,需要区分“启动阶段”和“稳定运行阶段”:
启动/预热阶段:JNI更胜一筹。FFM在首次调用时需要初始化核心库并创建`MethodHandle`,这会带来额外的启动成本(约几十到上百毫秒)。
稳定运行/高频调用阶段:FFM的优势完全体现出来。在完成预热后,FFM的调用效率会超越JNI。一份来自OpenJDK社区的实测数据显示,在达到约25万次调用的临界点后,FFM的总体耗时开始反超JNI。在持续高频调用的场景下(如UI渲染、高频计算),FFM的性能优势会更明显,典型表现为比JNI快约20%。

RMS

1344字节转换为float值,共336组,聚合方法--RMS--信号的(等效直流能力),反映了信号的强度或振幅

RMS归一化(均方根归一化)为[0,1]

八、WebSocket

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。因此,在WebSocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。

旧方案,否:

Java与前端怎么交互?通过 WebSocket 协议,Java是服务器端,浏览器是客户端

准备:

  • 确认JDK版本为JDK 21

  • C++ DLL导出函数的头文件
  • DLL文件的位置

C++

promise-future机制

九、JNI各文件

1.各个文件

1.1.TcpCommJni.java — Java端JNI接口定义

功能:Java与C++之间的桥梁接口,定义了所有可以从Java调用的C++本地方法

1.2.Main.java — Java端测试主程序

功能:独立的Java测试程序,用于验证JNI桥接功能是否正常工作。

1.3.jni_bridge.h — C++端JNI头文件

功能:定义JNI函数签名和全局上下文结构体。

1.4.jni_bridge.cpp — C++端JNI实现

功能定位 :实现所有JNI函数,负责Java与C++之间的数据转换和调用转发。

所有JNI函数都与AppController的方法一一对应!

除了 registerCallback() 是JNI层特有的(用于保存Java回调对象引用),其他所有JNI函数都直接调用了 AppController 的对应方法。

2.JNI函数命名规则

3.Main.java中创建JNI实例?

JNI是基于对象的。

虽然native方法实际上操作的是C++的全局单例(g_jni_context),但Java层面需要通过对象实例来调用native方法

4.JNI回调机制

回调:你告诉别人“做完后通知我”

JNI回调:Java告诉C++“当某个事件发生时,调用我的这个方法”

Java不会向C++回调,C++向Java回调!

Java调用C++,是直接调用native方法,不是回调。

先注册回调函数,等C++端完成时通知Java端,比如:当C++端控制通道连接成功,回调触发,通知Java端控制通道连接方法

JNI回调:C++是跑腿的,Java告诉C++当某个事做完/当触发某个事件时,C++告诉Java

做完某事:连接完成/发送完成--主动操作的结果

触发某个事件:收到数据/设备断开--被动发生的事

本质都是C++调用Java回调方法

5.回调转发

6.回调与回调转发

回调是直接调用,回调转发是"先处理再转发"。在JNI场景下,转发层负责语言转换、数据处理和线程安全。

为什么要回调转发?
语言切换:C++的回调参数事C++类型(std::string),不能直接传给Java方法

数据处理:C++收到原始字节数据,需要解析json格式和字节流后传给Java

线程安全:C++回调在C++线程中执行,需要Attach到Java虚拟机才能调用Java方法

6.匿名内部类

匿名内部类=没有名字+定义在内部+一次性使用的类

不能复用,不能有构造方法

匿名内部类实现接口(直接在参数位置创建,不需要单独定义类)

直接在使用的地方创建

7.注解--Java

@Override是Java的注解,表示"我要覆盖父类/接口的方法"。

告诉阅读者这是一个重写方法

8.C++调用Java:C++异步工作,需要通知Java事件

9.如果没有C++→Java回调会怎样?

Java只能 轮询 (不断问C++"有没有数据?")

轮询的问题 :

效率低 :大部分时间都在空等
延迟高 :最多延迟100ms才能收到数据
浪费资源 :占用CPU和线程


回调的优势 :

效率高 :只有有数据时才处理
零延迟 :数据到达立即通知
资源节约 :不需要轮询线程

10.JNI回调是"做完某事"还是"触发某个事件"?

两个都是,只是对同一个意思不同描述。

十、JNI基础

1.JNI

JNI(Java Native Interface) Java本地接口:是Java提供的一种机制,允许Java代码调用本地代码(C/C++),也允许本地代码调用Java代码。

2.JNI架构图

3.Java端知识点

3.1native方法声明

native关键字表示方法实现不在Java中,在本地代码中。

3.2加载本地库

3.3回调接口定义

3.4匿名内部类实现回调

3.5 @Override注解

4.C++端知识点

4.1JNI函数命名规则

4.2JNI函数签名

4.3 extern “C”导出

为什么需要extern "C" :

C++编译器会对函数名进行修饰(如 _Z4funcv )
Java虚拟机按照 Java_包名_类名_方法名 的规则查找函数
如果没有extern "C",函数名会被修饰,Java找不到

4.4JNI数据类型转换

4.5JNI全局上下文

JNI全局上下文是一个全局结构体,存储了JNI桥接层需要的所有状态和资源。


为什么需要全局上下文?JNI函数是独立的,需要一个共享的状态容器。

5.JNI回调机制

5.1回调注册

为什么需要全局引用 :

Java对象默认是局部引用,函数返回后会被垃圾回收
回调对象需要在C++线程中长期使用,必须创建全局引用
使用完后必须删除全局引用,否则内存泄漏

5.2C++向Java回调


5.3 调用Java方法的辅助函数

5.4 方法签名格式

jni_bridge.cpp

方法签名:为了让JNI找到正确的Java方法

Java支持方法重载(同名不同参数),JNI需要方法签名来区分。


6.JNI线程安全

6.1JNIEnv的线程相关性

JNIEnv* 是线程相关的!每个线程有自己的JNIEnv
不能跨线程使用同一个JNIEnv
应该在在C++线程中获取当前线程的JNIEnv

6.1.1AttachCurrentThread()与DetachCurrentThread()

是一个 JNI 函数,用于将当前 native 线程连接(attach)到 Java 虚拟机(JVM),从而让该线程能够进行 JNI 调用,访问 Java 对象和方法。

AttachCurrentThread:将C++线程附加到Java虚拟机(JVM),获取该线程的JNIEnv*。

AttachCurrentThread()DetachCurrentThread()必须成对调用,否则会导致线程资源泄漏,甚至 JVM 崩溃。

任何本地线程调用Java方法,都必须先attach获取自己的JNIEnv。



本项目每次回调都attach / detach?

Attach注册线程到JVM,Detach解除注册;仅首次需要Attach。


6.1.2DeleteLocalRef

释放局部引用,防止JNI局部引用表溢出。

为什么需要DeleteLocalRef?
防止局部引用泄漏,导致JNI局部引用表溢出!

6.2JavaVM的全局共享

JavaVM* 是全局共享的,可以跨线程使用

在JNI_OnLoad中保存JavaVM指针

6.3全局引用与局部引用

7.JNI生命周期

初始化和关闭顺序有严格要求,错误的顺序会导致崩溃、内存泄漏或资源未释放。

7.1初始化流程


初始化顺序依赖关系:

7.2关闭流程

关闭顺序重要:

停止控制器 → 释放工作守卫 → 停止IO上下文 → 等待线程 → 删除全局引用 → 重置状态

关闭顺序:

7.3总结

初始化:先基础设施,后业务
1. IO上下文 → 2. 线程池 → 3. 控制器 → 4. 初始化控制器 → 5. 注册回调 → 6. 工作守卫 → 7. 启动线程

关闭:先业务,后基础设施
1. 关闭控制器 → 2. 释放工作守卫 → 3. 停止IO上下文 → 4. 等待线程 → 5. 删除全局引用 → 6. 重置状态

关闭工厂:

1.告诉工人停止工作(关闭控制器)
2.等待工人离开工厂(等待线程结束)
3.拆楼(删除IO上下文)

关闭错误:




7.4 g_jni_context

g_jni_context是全局上下文对象,包含了JNI桥接层需要的所有状态和资源。

g_jni_context.control_io_context_ = std::make_unique<boost::asio::io_context>();
control_io_context_是std::make_unique<boost::asio::io_context>类型,是C++的Boost.Asio库,不是JNI方法。

7.5 控制器

AppController整个C++业务逻辑的核心。

控制器依赖IO上下文:控制器需要进行异步网络通信

初始化注册回调用到控制器:控制器是业务逻辑层,提供了回调注册接口。
不用IO上下文:IO上下文是底层事件循环,不是提供业务回调接口。

7.6 IO上下文

启动事件循环

IO上下文在调用run()方法后真正启动。

7.7 工作守卫

工作守卫:防止IO上下文因为没有任务而退出。

为什么要加入工作守卫:启动事件循环,当前没有异步操作,io.run()会立即返回,线程结束,后续的异步操作无法处理。加入,io.context()不会返回,因为工作守卫保持io_context“有工作”了,即使当前没有异步操作,io.run()也会等待,线程持续运行。

7.8总结

7.9 make_unique / make_shared

make_unique→ 独占、轻量、无计数,首选

make_shared→ 共享所有权、带引用计数、支持 weak_ptr,连续内存分配更快但内存回收有延迟

8.JNI编译与链接

8.1 DLL编译

8.2 Java头文件生成

9.常见错误与注意

9.1常见错误

9.2注意

使用全局引用保存跨线程使用的Java对象
在非Java线程中调用Java方法时,必须先AttachCurrentThread
使用完局部引用后及时DeleteLocalRef
JNI函数名必须严格按照Java_包名_类名_方法名格式
使用extern "C"导出JNI函数
关闭时按照正确的顺序释放资源

10.为什么要有JNI桥接层

解耦 :AppController不需要知道Java的存在
转换 :处理跨语言数据类型转换
线程安全 :处理跨线程调用问题
生命周期管理 :统一管理资源的创建和释放

11.回调驱动设计

Java不需要轮询,而是等待C++通知
事件驱动:数据到达 → C++回调 → Java处理 → 更新前端

JNI与FFM,JNI缺点,为什么要用FFM

Java线程分类

Tomcat线程

Tomcat是Spring Boot默认的嵌入式Web服务器,Tomcat线程就是处理HTTP请求的工作线程。

FFM各文件

1.tcp_comm_c_api.h - C API 头文件

extern "C"
作用 :定义 C 语言导出接口,作为 Java FFI 和 C++ 实现之间的桥梁。

2.tcp_comm_c_api.cpp - C API 实现
作用 :实现 C API 接口,内部封装 AppController ,管理双通道(控制通道 + 数据通道)的生命周期和数据流转。

3.TcpCommBridge.java - Java FFI 桥接层
作用 :使用 Java 25 FFM(Foreign Function & Memory API)调用 C DLL,提供类型安全的 Java API 包装。

十一、FFM基础

1.Linker链接器--FFM核心入口

Java与原生代码之间的桥梁,负责调用C函数

将C函数签名与Java方法对应

2.SymbolLookup符号查找--定位DLL中的函数

作用 :从加载的原生库中查找导出符号(函数名)。

3.FunctionDescriptor(函数符号描述)--定义C函数签名

作用 :用Java类型描述C语言的参数和返回值类型,FFM类型安全的基础。

4.ValueLayout(值布局)--类型映射表

作用 :定义 Java 类型与C类型在内存中的布局映射关系。FFM类型安全的关键。


注意 :C 头文件中的 bool 映射到 Java 的 ValueLayout.JAVA_BOOLEAN ,而不是 JAVA_BYTE 。

5.MemorySegment(内存段)--原生内存的Java表示--原生内存块核心类

作用 :表示一块原生内存区域,是 Java 和 C 之间共享数据的核心。

表示:

原生对象的句柄(从C返回的指针)

C字符串的内存区域

任意原生内存缓冲区

原生内存区域是 物理存在的内存块 ,MemorySegment 是 Java 世界用来 安全访问这块内存的带类型安全的代理对象 。没有 MemorySegment,Java 无法以类型安全的方式读写原生内存。

6.Arena(内存分配器)--临时内存管理

6.1Arena(内存分配器)--临时内存管理

作用 :管理 MemorySegment 的生命周期,避免内存泄漏。

负责分配和释放与原生代码交互所需要的临时内存。

关键特性 :

- Arena.ofConfined() :创建受限区域,自动管理内存
- try-with-resources :退出作用域时自动释放所有分配的内存
- 解决了旧 JNI 中手动管理 malloc/free 的痛点

6.2内存生命周期与Resource Leak 防护

两种内存来源 :

1. Arena 分配的临时内存 :try-with-resources 自动释放
2. C 端 strdup/new 分配的内存 :需要显式调用 tcp_comm_free_string 或对应释放函数
关键 :C 头文件中如果有返回 char* 或其他需要释放的资源,必须提供对应的释放 API。

try-with-resources 它确保在 try 块执行完毕后, 无论是否发生异常 ,都会自动调用资源的 close() 方法来释放资源。

问题:每个 Arena.ofShared() 都会创建一个新的共享Arena,且全程没有调用 close() 。

影响:原生内存泄漏,每个stub占用的资源无法释放;持续累积内存泄露

改进:复用stub,显示管理Shared Arena的生命周期
将所有Stub创建在同一个Shared Arena中
在 globalShutdown() 时关闭Arena
适合本项目:所有handle共享同一个Stub

7.MethodHandle与Handle

7.1MethodHandle

作用 :封装原生函数调用,提供类型安全的调用接口。

7.2Handle

C 端通过 void* 表示内部对象,Java 端通过 MemorySegment 表示这个指针。这是跨语言状态管理的核心模式。

8.Java String 到 C char* 的转换

作用 :将 Java String 转换为 C 兼容的 null 终止字符串。FFM不自动处理String,需要手动编码。

9.缓冲区传递模式(Buffer-Passing)

作用 :Java 预先分配缓冲区,C 侧填充数据后 Java 读取。

设计原因 :

避免 C 返回分配的字符串导致内存泄漏
符合项目约束: tcp_comm_dual_get_last_frame_hex 必须使用缓冲区传递

10.句柄检查(Handle Validation)

作用 :防止空指针和无效句柄导致的崩溃。

11.Downcall与Upcall

向下调用与向上调用

11.1向下调用:Java 调用 DLL,Java调用方,C++被调用方。

通过 MethodHandle.invoke() 从 Java 直接调用 C 函数。

11.2向上调用:DLL调用Java ,C++调用方,Java 被调用方

实现真正的FFM upcall机制

12.DLL加载机制

Java 通过 System.load() 加载原生库,必须指定完整路径。

13.静态初始化块中的绑定注册

所有 FFM 绑定注册在 static {} 块中完成,确保类加载时一次性建立所有原生调用桥。

14.Stub=回调跳板

Stub(桩函数) 是 FFM中用于实现 C++ 调用 Java 回调的关键机制。

stub=C接口外壳+Java实现内核。

stub解决:C++无法直接调用Java方法,需要一个C语言接口。C++只能理解C函数指针,无法理解Java函数引用。

生命周期:Arena创建,手动close释放

Stub 不是普通的 Java 方法,而是通过 LINKER.upcallStub() 动态生成的,特点:
它是一块分配在 原生内存 中的代码;符合 C 调用约定 (栈布局、参数传递、返回值);内部包含 JVM 调用桥接逻辑。


GC垃圾回收

GC 是 JVM 自动清理无效 Java 对象、回收内存的机制;它会产生 STW 停顿,是 JNI/FFM 性能测试中长尾延迟最重要的干扰因素之一

  • P50(中位数):代表普通水平,看不到长尾
  • P99 / P999:专门用来捕捉长尾延迟P99 越高,说明尾巴拖得越长,系统抖动越严重。

你表格里的现象完美印证: 每组 1000 个样本单独统计时,P99 只有 262μs; 合并 10000 条全局统计,P99 直接涨到 524μs。 原因:样本量变多,更容易采集到罕见的长尾慢请求,长尾效应显现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询