如何从原生C代码通过JNI调用Java函数,无需Java进程先启动?
问题核心结论
首先明确两个问题的答案:
1. 能否不依赖JVM调用Java函数?
绝对不可以。Java代码编译后是字节码格式,必须依赖JVM提供的字节码解析、即时编译、内存管理、方法调度、异常处理等整套运行时能力才能执行,不存在脱离JVM运行Java函数的方案。
2. 能否从原生C代码作为入口加载JVM再调用Java函数?
完全可以,这是JNI规范原生支持的场景,你遇到的JNI_CreateJavaVM不存在的问题是你使用的libjvm.so版本/路径错误导致的。
问题排查与实现方案
排查JNI_CreateJavaVM不存在的问题
JNI_CreateJavaVM是JNI规范明确要求JVM导出的原生入口,只有位于JDK/JRE内部的server版本libjvm.so才会导出该符号:
- 常规OpenJDK/OracleJDK中,该库的路径为
<JDK安装路径>/jre/lib/<CPU架构>/server/libjvm.so(JDK8及更早版本),或者<JDK安装路径>/lib/server/libjvm.so(JDK9及以上无独立jre目录的版本) - 可以用命令
nm -D libjvm.so | grep JNI_CreateJavaVM验证符号是否存在,如果你用的是裁剪过的定制JRE、或者client版本的libjvm.so,就会出现符号不存在的问题,替换为标准server版本的libjvm.so即可解决。
原生C代码启动JVM的实现步骤
不需要提前启动Java进程,直接在C代码中动态加载JVM即可:
- 用
dlopen加载正确路径的libjvm.so - 用
dlsym获取JNI_CreateJavaVM的函数指针 - 配置JVM启动参数(类路径、堆大小等),调用该函数创建JVM实例,拿到
JavaVM*和JNIEnv*指针 - 用
JNIEnv*的相关接口查找目标Java类、方法ID,执行Java函数 - 跨线程调用Java函数前,需要先调用
JavaVM*的AttachCurrentThread方法将当前线程附着到JVM上,获取当前线程可用的JNIEnv*指针
示例代码片段
#include <dlfcn.h> #include <jni.h> #include <stdio.h> int main() { // 动态加载libjvm.so void* libjvm = dlopen("/path/to/jdk/lib/server/libjvm.so", RTLD_LAZY); if (!libjvm) { printf("加载libjvm.so失败: %s\n", dlerror()); return 1; } // 获取JNI_CreateJavaVM函数指针 jint (*JNI_CreateJavaVM)(JavaVM**, JNIEnv**, JavaVMInitArgs*) = (jint (*)(JavaVM**, JNIEnv**, JavaVMInitArgs*))dlsym(libjvm, "JNI_CreateJavaVM"); if (!JNI_CreateJavaVM) { printf("获取函数指针失败: %s\n", dlerror()); dlclose(libjvm); return 1; } // 配置JVM参数 JavaVMOption options[1]; options[0].optionString = "-Djava.class.path=/path/to/your/java/classes"; JavaVMInitArgs vm_args; vm_args.version = JNI_VERSION_1_8; // 根据你的JDK版本调整 vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_FALSE; // 创建JVM JavaVM* jvm; JNIEnv* env; jint res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (res != JNI_OK) { printf("创建JVM失败: %d\n", res); dlclose(libjvm); return 1; } // 接下来就可以正常调用JNI接口执行Java代码了 // 示例:调用java.lang.System.out.println jclass system_cls = (*env)->FindClass(env, "java/lang/System"); jfieldID out_fid = (*env)->GetStaticFieldID(env, system_cls, "out", "Ljava/io/PrintStream;"); jobject out_obj = (*env)->GetStaticObjectField(env, system_cls, out_fid); jclass printstream_cls = (*env)->GetObjectClass(env, out_obj); jmethodID println_mid = (*env)->GetMethodID(env, printstream_cls, "println", "(Ljava/lang/String;)V"); jstring str = (*env)->NewStringUTF(env, "原生C调用Java成功"); (*env)->CallVoidMethod(env, out_obj, println_mid, str); // 销毁JVM (*jvm)->DestroyJavaVM(jvm); dlclose(libjvm); return 0; }
注意事项
- 运行C程序的环境必须安装对应版本的JRE/JDK,不存在完全脱离Java运行时环境执行Java代码的方案
- 所有要调用的Java类必须配置到JVM启动参数的
java.class.path中,否则会出现类找不到的错误 - 程序退出前记得调用
DestroyJavaVM销毁JVM实例,释放资源
内容的提问来源于stack exchange,提问作者Hanseungkyun
相关产品推荐
相关产品推荐

