Java Native Interface 应用: 允许 Java 代码与用其他编程语言(如 C、C++)编写的应用程序和库进行交互
调用本地库 , 与操作系统特定功能交互 , 性能优化 (关键部分用系统本地语言实现,JNI调用实现)。
某些算法库等中间件是由c/c++实现
加载时机
- 在类的静态初始化块中加载本地库 (少数地方调用库时)
- 在 Application 初始化时加载本地库 (程序启动时加载)
java端
package JNITest;
public class JNITestDemo {
//native 用来声明本地方法(java之外实现)
public native void helloWord();
//在main调用之前加载动态库
static {
System.loadLibrary("hello"); // 加载本地库,库名为 libhello.so 或 hello.dll
}
public static void main(String[] args) {
//创建实例调用
new JNITestDemo().helloWord();
}
}
生成JNITest_JNITestDemo.h文件
用utf-8编码格式编译
javac -encoding UTF-8 -h . JNITest\JNITestDemo.java
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest_JNITestDemo */
#ifndef _Included_JNITest_JNITestDemo
#define _Included_JNITest_JNITestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest_JNITestDemo
* Method: helloWord
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
编译选项
启动参数添加https://blog.csdn.net/Saintmm/article/details/124603343
java -Djava.library.path=F:\workProject\clionProject\StudyProject\cmake-build-debug-visual-studio\JNITest 告诉 JVM 在哪里查找本地库文件
c端库编译
https://cloud.tencent.com/developer/article/1847548
拷贝头文件到项目
#include "stdio.h"
#include "jni_md.h"
#include "JNITest_JNITestDemo.h"
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord(JNIEnv *env, jobject obj)
{
printf("hello word");
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class JNITest_JNITestDemo */
#ifndef _Included_JNITest_JNITestDemo
#define _Included_JNITest_JNITestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest_JNITestDemo
* Method: helloWord
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord(JNIEnv *env, jobject obj);
#ifdef __cplusplus
}
#endif
#endif
- *JNIEnv env:指向 JNI 环境的指针。
- jobject obj:指向调用该本地方法的 Java 对象的引用
- JNIEnv 概念和作用
线程相关性:JNIEnv 是一个与线程相关的结构体。每个 Java 线程都有自己的 JNIEnv 实例,它反映了该线程在 Java 虚拟机中的运行环境。
代表 Java 运行环境:通过 JNIEnv,JNI 本地方法可以与 Java 虚拟机进行交互,包括调用 Java 方法、访问字段、创建对象等操作。因此,JNIEnv 可以看作是本地方法与 Java 虚拟机通信的桥梁。 - 使用 JNIEnv
调用 Java 方法:JNI 本地方法可以使用 JNIEnv 调用 Java 中的方法,包括静态方法和实例方法。
操作 Java 对象:Java 对象在 JNI 层以 jobject 类型传递,JNI 方法可以使用 JNIEnv 操作这些 Java 对象,例如获取字段值、设置字段值、调用方法等操作。
方法遵循一定命名规范,让jvm正确找到调用。
cmake_minimum_required(VERSION 3.27.8)
project(StudyProject C)
set(CMAKE_C_STANDARD 99)
add_executable(StudyProject main.c)
add_subdirectory(./JNITest ${CMAKE_CURRENT_BINARY_DIR}/JNITest)
cmake_minimum_required(VERSION 3.27.8)
aux_source_directory(./ DIR_SRCS)
add_library(hello SHARED ${DIR_SRCS})
产物
参数添加
package JNITest;
public class JNITestDemo {
public native void helloWord(String message); // 声明 native 方法
// 加载本地库
static {
System.loadLibrary("hello");
}
public static void main(String[] args) {
// 创建实例并调用 native 方法
JNITestDemo demo = new JNITestDemo();
demo.helloWord("Hello from Java!"); // 传递字符串参数给 native 方法
}
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest_JNITestDemo */
#ifndef _Included_JNITest_JNITestDemo
#define _Included_JNITest_JNITestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest_JNITestDemo
* Method: helloWord
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
GetStringUTFChars
将jstring对象转换为C/C++的UTF-8编码的字符数组
ReleaseStringUTFChars释放
#include "stdio.h"
#include "jni_md.h"
#include "JNITest_JNITestDemo.h"
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord(JNIEnv *env, jobject obj, jstring s)
{
const char *c_message = (*env)->GetStringUTFChars(env, s, NULL);
if (c_message == NULL) {
return; // 内存分配失败
}
printf("JNI test hello word %s", c_message);
// 释放字符串
(*env)->ReleaseStringUTFChars(env, s, c_message);
}
注意事项
内存管理
- 释放本地资源:
- 使用 NewGlobalRef 创建的全局引用,在使用完毕后需要通过 DeleteGlobalRef 明确释放,避免内存泄漏。
- 在使用 GetObjectClass、GetFieldID、GetMethodID 等获取的局部引用,也需要及时释放。
- 处理字符串和数组:
- 使用 GetStringUTFChars、GetStringChars 获取字符串内容后,需要通过 ReleaseStringUTFChars、ReleaseStringChars 释放对应资源。
- 对于数组操作,使用 GetXXXArrayElements 获取数组元素后,必须使用对应的 ReleaseXXXArrayElements 进行释放。
- 异常处理:
- JNI方法中的异常不会自动传播到Java层,需要手动处理和清除异常状态,通常通过 ExceptionCheck、ExceptionDescribe 等方法进行异常处理和调试。
线程安全性
- JNIEnv对象的线程限制:
- JNIEnv 对象只能在创建它的线程中使用,不能跨线程传递。因此,JNI方法中的JNIEnv对象只能用于当前线程中,不能保存或者跨线程使用。
- 多线程操作:
- 如果JNI方法需要在多线程环境下使用,确保正确处理同步和竞态条件。可以使用Java层的同步机制,如synchronized关键字,来保护共享资源。
性能考虑
- 避免频繁的JNI调用:
- JNI调用涉及到Java和本地代码的上下文切换,会带来一定的性能开销。因此,尽量避免在循环中频繁调用JNI方法,考虑批量处理或者优化算法以减少JNI调用次数。
- 使用本地缓存:
- 对于需要频繁访问的Java对象或数据,可以考虑在本地缓存中保留引用,避免每次调用都重新获取引用,提高访问效率
常用数据转换函数
字符串操作
- GetStringUTFChars / ReleaseStringUTFChars
- const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
- void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
- 将Java字符串(jstring)转换为C字符串(UTF-8编码),并在使用后释放。这对于在本地代码中操作Java字符串非常有用。
- GetStringChars / ReleaseStringChars
- const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
- void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
- 将Java字符串(jstring)转换为C字符串(Unicode编码),并在使用后释放。适用于需要直接访问Java字符串字符数组的情况。
- NewStringUTF
- jstring NewStringUTF(JNIEnv *env, const char *utf);
- 根据UTF-8编码的C字符串创建一个新的Java字符串(jstring)对象。常用于将C字符串转换为Java字符串后传递给Java方法。
数组操作
- GetXXXArrayElements / ReleaseXXXArrayElements
- jbooleanArray GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy);
- void ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode);
- 获取和释放Java数组的元素,支持boolean、byte、char、short、int、long、float、double等类型的数组操作。
- NewXXXArray / GetXXXArrayRegion / SetXXXArrayRegion
- jintArray NewIntArray(JNIEnv *env, jsize length);
- void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf);
- void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf);
- 创建新的Java数组(int数组),以及获取和设置数组的指定区域。
基本数据类型操作
- GetXXXField / SetXXXField
- jobject GetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID);
- void SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value);
- 获取和设置Java对象的字段值,支持各种基本数据类型以及对象类型的字段操作。
- CallXXXMethod
- jobject CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID, …);
- 调用Java对象的实例方法,支持返回对象、基本数据类型等不同类型的方法调用。
全局引用管理
- NewGlobalRef / DeleteGlobalRef
- jobject NewGlobalRef(JNIEnv *env, jobject obj);
- void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
- 创建和删除全局引用,全局引用可以在JNI方法之间传递,并且可以长期保存Java对象的引用。