如何通过JNI从已运行的JVM中获取JavaVM或JNIEnv实例
崩溃原因
你当前代码崩溃的核心问题是给JNI_GetCreatedJavaVMs传入了空指针作为输出参数。该函数的三个参数定义如下:
- 用于接收JVM实例指针的缓冲区地址
- 要获取的JVM实例最大数量
- 用于接收实际获取到的JVM数量的输出变量地址
你定义的jvm_ptr和count都是空指针,没有对应可写入的合法内存空间,函数执行时尝试向空地址写入数据,直接触发内存访问违例崩溃。
实现方法
DLL注入到Java进程后完全可以正常获取JNI环境,不需要Java侧主动发起调用,整体实现流程如下:
- 提前在栈上分配内存存储JVM指针和JVM数量,合法传入
JNI_GetCreatedJavaVMs拿到已初始化的JavaVM实例 - 通过JavaVM指针调用
AttachCurrentThread将当前注入的执行线程附加到JVM,拿到当前线程专属的JNIEnv指针,即可正常调用所有JNI能力 - 所有JNI操作完成后,调用
DetachCurrentThread将线程从JVM分离,避免资源泄漏
核心实现代码参考:
use jni::sys::{ JNI_GetCreatedJavaVMs, JNIEnv, JNI_VERSION_1_8, JavaVM, jint }; use std::ptr::null_mut; // 直接在栈上定义可变变量分配内存,不要传空指针 let mut jvm: *mut JavaVM = null_mut(); let mut jvm_count: jint = 0; // 传入变量的可变地址作为输出缓冲区 let get_vm_status = unsafe { JNI_GetCreatedJavaVMs( &mut jvm as *mut *mut JavaVM, 1, &mut jvm_count as *mut jint ) }; // JNI_OK对应值为0,返回非0、数量为0、指针为空都属于获取失败 if get_vm_status != 0 || jvm_count < 1 || jvm.is_null() { // 自行处理获取JVM失败的逻辑 return; } // 附加当前线程到JVM,获取当前线程专属的JNIEnv let mut env: *mut JNIEnv = null_mut(); let attach_status = unsafe { (**jvm).AttachCurrentThread.unwrap()( jvm, &mut env as *mut *mut JNIEnv as *mut *mut _, null_mut() ) }; if attach_status != 0 || env.is_null() { // 自行处理线程附加失败的逻辑 return; } // 此处拿到的env可正常用于所有JNI调用,比如查找类、调用Java方法等 // JNI操作全部完成后,分离当前线程 unsafe { (**jvm).DetachCurrentThread.unwrap()(jvm); }
避坑要点
- 调用
JNI_GetCreatedJavaVMs前先确认目标进程的jvm.dll已经加载完成、JVM初始化完毕,注入时机过早会导致拿不到JVM实例,甚至因为函数地址无效崩溃。 - JNIEnv是线程绑定资源,禁止跨线程传递使用,其他线程需要调用JNI时必须单独执行Attach操作获取自己的env。
- 注入的DLL必须和目标Java进程位数完全一致,32位、64位混用会直接触发内存错误。
- Attach线程时指定的JNI版本要和目标Java版本匹配,日常使用传
JNI_VERSION_1_8即可兼容Java 8及以上绝大多数版本。
内容的提问来源于stack exchange,提问作者majorsopa
相关产品推荐
相关产品推荐

