1.JNI的应用场景
Java需要调用C/C++库:操作系统API、硬件驱动、加密算法、遗留代码。JNI是标准桥接技术。但不当使用JNI会带来性能损失和内存崩溃。正确实践能接近本地性能。
参考:https://oqmyh.cn/category/kang-shuailao.html
2.减少边界开销
批处理:避免在循环中逐个调用native方法,而是传入数组,在native端处理整个数组。
直接缓冲区:使用ByteBuffer.allocateDirect,native代码可以直接访问内存,避免复制。
Critical数组:使用GetPrimitiveArrayCritical获取指针,但会阻塞GC,需尽快释放。
缓存jmethodID/jfieldID:在native代码中缓存,避免每次调用GetMethodID。
3.内存管理
释放本地引用:DeleteLocalRef,否则可能导致本地引用表溢出(默认512个)。
全局引用:NewGlobalRef需配对DeleteGlobalRef,否则内存泄漏。
异常检查:调用Java方法后需ExceptionOccurred并处理。
4.使用JNI的性能比较
一个图像处理任务:纯Java实现vs调用C++OpenCV。JNI版本使用GetDirectBufferAddress直接操作像素数据,耗时12ms;Java版本使用BufferedImage操作需85ms。但JNI调用本身每次约50ns,几乎可忽略。
参考:https://oqmyh.cn/category/hufu-chengfen.html
5.案例:密码学加速
某金融系统需要高频签名(SM2算法)。Java实现的签名速度慢,使用JNI调用C++的GMSSL库。策略:
一次JNI调用处理一批签名请求(数组),避免多次JNI边界切换。
使用DirectBuffer传递签名参数。
在native层管理签名上下文对象,Java层持有long指针,避免创建新对象。
最终签名吞吐量提升10倍。
6.替代方案
Java外部内存访问API(ForeignMemoryAccess):Java14预览,18正式,比JNI更简单。
JNA:动态调用,无需生成头文件,但性能低于JNI。
ProjectPanama:未来替代JNI。
7.总结
JNI是Java调用本地代码的经典方式。正确使用批处理、直接缓冲区和缓存,可以获得接近原生的性能。对于需要复用大量C/C++库的Java项目,JNI是必不可少的工具。
参考:https://oqmyh.cn