System.loadLibrary("xxx")按平台自动补全前缀后缀(如Linux找libxxx.so),需确保文件名规范、在java.library.path中、JNI符号匹配且未重复加载。

System.loadLibrary() 加载的是库名,不是文件名
调用 System.loadLibrary("foo") 时,JVM 不会去找 foo.so(Linux)或 foo.dll(Windows),而是按平台规则自动补全前缀和后缀:Linux 上搜索 libfoo.so,Windows 上搜索 foo.dll,macOS 上搜索 libfoo.dylib。如果编译出的文件叫 mylib.so,却写 loadLibrary("mylib"),那没问题;但如果叫 mylib_v2.so,就不能直接用 "mylib_v2"——因为 JVM 会去找 libmylib_v2.so,而实际文件没带 lib 前缀,会报 UnsatisfiedLinkError: no mylib_v2 in java.library.path。
常见错误现象:UnsatisfiedLinkError 提示“no xxx in java.library.path”,但文件明明存在。此时先确认文件名是否符合命名规范,再检查是否在 java.library.path 路径中。
- 编译 C/C++ 时,Linux 下建议用
gcc -shared -fPIC -o libxxx.so xxx.c,确保前缀为lib - Windows 下 DLL 文件可不加
lib前缀,但 Java 侧仍用不含前缀、不含后缀的库名(如xxx.dll→loadLibrary("xxx")) - macOS 对大小写和前缀更敏感,
libXXX.dylib和loadLibrary("xxx")会失败,必须大小写一致
java.library.path 决定搜哪找,不是当前目录
System.loadLibrary() 只在 java.library.path 指定的路径里搜索,**不会自动查当前工作目录或 classpath**。即使把 libfoo.so 放在 src/main/resources 或 jar 包同级目录,也不起作用。
验证当前路径:启动时加 -Djava.library.path=/path/to/libs,或运行时打印 System.getProperty("java.library.path") 看实际值。
- 推荐做法:启动 JVM 时显式指定,例如
java -Djava.library.path=./libs MyApp - 开发期调试可用
System.setProperty("java.library.path", "/absolute/path/to/libs"),但注意这不能改变已初始化的 ClassLoader 的行为,且需在loadLibrary()前调用 - Android 不支持
System.loadLibrary()加载非lib/目录下的 so(必须放在src/main/jniLibs/对应 ABI 子目录下)
Native 方法声明与 .so 导出符号必须严格匹配
Java 层声明的 native 方法,其 JNI 函数名由包名 + 类名 + 方法名拼接生成(如 com.example.Foo.bar() 对应 C 函数 Java_com_example_Foo_bar)。如果 C 侧函数名拼错、漏了 JNIEnv* 或 jclass 参数、返回类型不一致,加载虽成功,但首次调用时抛 UnsatisfiedLinkError: Native method not found。
容易被忽略的点:C++ 编译时若未用 extern "C" 包裹函数定义,会导致符号名被 C++ 编译器修饰(mangled),JNI 找不到原始符号。
- 务必用
javah(旧版)或javac -h(JDK 8+)生成头文件,照着实现 - C++ 实现中,每个
JNIEXPORT函数外层加extern "C" { ... } - 使用
nm -D libxxx.so(Linux)或objdump -t libxxx.so | grep Java_检查导出符号是否存在且拼写正确
重复 loadLibrary 或跨 ClassLoader 加载会失败
同一个库名对一个 ClassLoader 只能成功加载一次。System.loadLibrary("foo") 第二次调用直接返回,不报错;但若在不同 ClassLoader(如 Web 容器中多个 webapp)里分别加载同名库,可能触发 UnsatisfiedLinkError: Native Library /path/libfoo.so already loaded in another classloader。
这不是 bug,是 JVM 的安全限制——本地库全局共享,无法隔离。
- 避免在热部署场景反复加载;如需重载,改用
System.load("/absolute/path/to/libfoo.so")(绝对路径),它不走库名解析,每次都是新加载(但仍有风险,慎用) - 多模块共用同一 native 库时,确保只由最上层 ClassLoader 加载,子 ClassLoader 不要重复调用
- Android 中,每个 APK 的 so 是独立加载的,但同一进程内相同 so 名仍受此限制
真正麻烦的从来不是“怎么写那一行代码”,而是名字对不对、路径在不在、符号导出没导出、ClassLoader 隔离没隔离——这些地方一错,错误信息都长得差不多,得一层层剥开看。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











