Java通过JNI调用C++的规范流程是:声明native方法→生成头文件→C++按签名实现→编译为动态库→Java加载调用;需注意extern "C"、函数命名、字符串API使用及架构匹配。

Java 通过 JNI(Java Native Interface)机制调用 C++ 代码,这是官方支持、稳定可靠的方式。核心思路是:Java 声明 native 方法 → 编译生成头文件 → C++ 实现该方法 → 编译为动态库 → Java 加载并调用。
一、Java 端准备:声明 native 方法并生成头文件
写一个 Java 类,用 native 关键字声明要由 C++ 实现的方法,并确保类已编译:
public class Calculator {
static {
System.loadLibrary("calculator"); // 加载名为 libcalculator.so(Linux)或 calculator.dll(Windows)的本地库
}
public native int add(int a, int b);
public native String sayHello(String name);
}
进入该 .class 文件所在目录,运行命令生成 JNI 头文件:
javac Calculator.java javah -jni Calculator
会生成 Calculator.h,其中包含类似 JNIEXPORT jint JNICALL Java_Calculator_add 的函数签名——这就是你要在 C++ 中实现的函数名,不能拼错。
立即学习“Java免费学习笔记(深入)”;
二、C++ 端实现:包含头文件,按签名编写函数
新建 calculator.cpp,包含必要的头文件,并严格按生成的函数名和参数类型实现:
#include#include extern "C" { // 防止 C++ 名字修饰,确保函数符号可被 Java 正确找到 JNIEXPORT jint JNICALL Java_Calculator_add(JNIEnv *env, jobject obj, jint a, jint b) { return a + b; } JNIEXPORT jstring JNICALL Java_Calculator_sayHello(JNIEnv *env, jobject obj, jstring name) { const char *str = env->GetStringUTFChars(name, nullptr); std::string hello = "Hello, " + std::string(str); env->ReleaseStringUTFChars(name, str); // 必须释放,否则内存泄漏 return env->NewStringUTF(hello.c_str()); } }
注意要点:
- 必须用
extern "C"包裹函数,否则链接失败 - 函数名、参数顺序、返回类型必须与 .h 文件完全一致
- 字符串操作要用
env提供的 API(如 GetStringUTFChars / NewStringUTF),不能直接用 C++ string 转换 - jobject 参数代表调用该方法的 Java 对象(若为 static 方法可忽略)
三、编译为动态库:匹配平台与 JDK 架构
根据操作系统选择对应命令,确保使用的 javac 和 javah 来自同一 JDK,且编译器架构(x86/x64)一致:
Linux(生成 libcalculator.so):
g++ -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux \
calculator.cpp -o libcalculator.so
macOS(生成 libcalculator.dylib):
g++ -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin \
calculator.cpp -o libcalculator.dylib
Windows(生成 calculator.dll,需 MinGW 或 MSVC):
g++ -shared -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" \
calculator.cpp -o calculator.dll
关键点:
-
-I指向 JDK 的include及子目录(如 linux/win32/darwin),否则找不到 jni.h - 必须加
-shared(Linux/macOS)或对应 DLL 编译选项(Windows) - 输出库名要与
System.loadLibrary("xxx")中的名字一致(不带前缀 lib/后缀 .so/.dll)
四、运行与调试常见问题
将生成的动态库放在 Java 运行时能加载到的位置(如当前目录、LD_LIBRARY_PATH、java.library.path 指定路径),然后运行:
java Calculator
典型报错及对策:
-
UnsatisfiedLinkError: no xxx in java.library.path→ 库文件名不对、路径未配置、或系统位数(32/64)与 JVM 不匹配 -
UnsatisfiedLinkError: ... wrong ELF class(Linux)→ 编译目标架构与 JVM 不一致(如用 32 位 g++ 编译,却用 64 位 java 运行) - 程序崩溃或乱码 → 字符串未正确释放(GetStringUTFChars 后没 Release)、空指针未判空、JNIEnv 在线程间误用(每个线程需独立 Attach/Detach)
进阶提示:JNI 函数调用开销较大,高频场景建议批量传参;复杂对象交互可用 JNI 提供的 GetObjectField / SetObjectField 等 API;生产环境推荐结合 JNA(更轻量)或 GraalVM(AOT 直接互调)替代部分 JNI 场景。











