在Java中调用共享库(.so
文件,Linux/Unix系统的动态链接库)需通过Java Native Interface(JNI)实现,以下是详细步骤和关键注意事项:
核心流程
编写Java类声明Native方法
public class NativeLibLoader { // 声明native方法 public native void printHello(); // 加载.so文件(不含"lib"前缀和扩展名) static { System.loadLibrary("hello"); } public static void main(String[] args) { new NativeLibLoader().printHello(); } }
- 关键点:
native
关键字标记本地方法。System.loadLibrary("hello")
加载名为libhello.so
的文件。
生成JNI头文件
javac NativeLibLoader.java javac -h . NativeLibLoader.java # JDK 10+ 推荐 # 或使用旧版命令:javah -jni NativeLibLoader
生成头文件NativeLibLoader.h
如下:
JNIEXPORT void JNICALL Java_NativeLibLoader_printHello(JNIEnv *, jobject);
编写C/C++实现
创建hello.c
实现头文件中的函数:
#include <jni.h> #include "NativeLibLoader.h" JNIEXPORT void JNICALL Java_NativeLibLoader_printHello(JNIEnv *env, jobject obj) { printf("Hello from C!n"); }
编译生成.so文件
使用GCC编译(以Linux为例):
gcc -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/linux" -shared -fPIC -o libhello.so hello.c
- 参数说明:
-I
:指定JDK头文件路径(Windows为include/win32
)。-shared -fPIC
:生成位置无关代码的动态库。-o libhello.so
:输出文件名必须以lib
开头。
运行Java程序
java -Djava.library.path=. NativeLibLoader
输出:Hello from C!
java.library.path
:指定.so文件的搜索路径(默认为系统库路径)。
关键注意事项
-
路径问题
.so
文件需置于java.library.path
包含的目录中。- 通过
System.getProperty("java.library.path")
查看默认路径。
-
命名规范
- 动态库名称必须为
lib<name>.so
,Java加载时使用System.loadLibrary("<name>")
。
- 动态库名称必须为
-
跨平台兼容性
- Windows需编译为
.dll
,macOS为.dylib
。 - 使用条件编译处理平台差异:
#if defined(__linux__) // Linux代码 #elif defined(_WIN32) // Windows代码 #endif
- Windows需编译为
-
内存管理
- JNI中分配的内存需手动释放(如
NewStringUTF
创建的字符串)。 - 避免跨JNI边界传递大对象,防止内存泄漏。
- JNI中分配的内存需手动释放(如
-
异常处理
- 在JNI函数中检查异常:
jthrowable exc = (*env)->ExceptionOccurred(env); if (exc) { (*env)->ExceptionClear(env); // 处理异常 }
- 在JNI函数中检查异常:
-
线程安全
- JNIEnv指针是线程局部的,不可跨线程使用。
- 多线程调用时通过
AttachCurrentThread
获取当前线程的JNIEnv。
最佳实践
-
安全性
- 验证.so文件来源,防止恶意代码注入。
- 使用
System.load()
的绝对路径替代loadLibrary()
以增强可控性。
-
性能优化
- 减少JNI调用次数(如批量处理数据)。
- 使用
Critical
区域直接访问原生数组(谨慎使用,避免长时间阻塞GC)。
-
错误排查
- 运行时报错
UnsatisfiedLinkError
:- 检查库路径是否正确。
- 使用
ldd libhello.so
验证依赖项(Linux)。
- 符号未找到:确保C函数名与JNI头文件完全一致。
- 运行时报错
-
替代方案
Java调用.so文件的核心是通过JNI桥接Java与原生代码,流程包括:声明Native方法 → 生成头文件 → 实现C代码 → 编译动态库 → 配置路径加载,开发者需关注平台兼容性、内存管理和线程安全,对于高性能或复杂集成场景,建议结合具体需求选择JNI或更高级封装库。
引用说明参考Oracle官方文档JNI Specification、GCC编译指南,以及实践中的常见问题解决方案。
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/44609.html