文章目录
本文是《深入理解 JNI》专题的第 5 篇,共 5 篇:
全局引用与弱全局引用
在JNI编程中,管理对象引用的生命周期是非常重要的。JNI提供了几种不同类型的引用,以适应不同的使用场景。其中,全局引用(Global Reference)和弱全局引用(Weak Global Reference)是两种常用的引用类型。
全局引用(Global Reference)
全局引用是JNI中最强的引用类型。一旦一个Java对象被全局引用指向,它就不会被垃圾收集器回收,即使所有其他的引用都已经超出作用域。全局引用通常用于以下场景:
创建全局引用的函数是`JNIEnv`的`NewGlobalRef`。使用全局引用时需要注意,因为它们会阻止对象被垃圾回收,所以必须谨慎管理,避免内存泄漏。
示例代码:创建和使用全局引用
JNIEXPORT void JNICALL Java_ClassName_createGlobalRef(JNIEnv *env, jobject obj) { // 创建全局引用 jobject globalRef = (*env)->NewGlobalRef(env, obj); if (globalRef == NULL) { return; // 创建失败 } // ... 使用全局引用 ... // 使用完毕后删除全局引用 (*env)->DeleteGlobalRef(env, globalRef); }
弱全局引用(Weak Global Reference)
弱全局引用是一种较弱的全局引用,它允许垃圾收集器回收被引用的Java对象。弱全局引用通常用于以下场景:
创建弱全局引用的函数是`JNIEnv`的`NewWeakGlobalRef`。使用弱全局引用时,需要注意检查对象是否已经被回收。
示例代码:创建和使用弱全局引用
JNIEXPORT void JNICALL Java_ClassName_createWeakGlobalRef(JNIEnv *env, jobject obj) { // 创建弱全局引用 jobject weakGlobalRef = (*env)->NewWeakGlobalRef(env, obj); if (weakGlobalRef == NULL) { return; // 创建失败 } // ... 使用弱全局引用 ... // 检查对象是否已被回收 jobject ref = (*env)->NewLocalRef(env, weakGlobalRef); if (ref != NULL) { // 对象仍然存在 (*env)->DeleteLocalRef(env, ref); } else { // 对象已被回收 } // 删除弱全局引用 (*env)->DeleteWeakGlobalRef(env, weakGlobalRef); }
注意事项
通过使用全局引用和弱全局引用,JNI提供了一种灵活的方式来管理Java对象的生命周期,使得本地代码可以在需要时保持对对象的引用,同时又不阻碍垃圾收集器的正常工作。
异常处理
在JNI编程中,处理异常是一个重要的环节。Java代码可能会抛出异常,而这些异常需要在本地代码中进行适当的处理。JNI提供了一组函数来检查和处理Java异常。
检查异常
当Java方法调用发生异常时,相关的JNI函数会返回错误代码,并且可以通过`JNIEnv`指针的`ExceptionOccurred`函数来检查是否有异常发生。
jthrowable exception = (*env)->ExceptionOccurred(env); if (exception != NULL) { // 异常发生 }
清除异常
如果检测到异常,可以使用`ExceptionClear`函数来清除异常状态。这通常在处理完异常后进行,以便JNI调用链可以继续正常执行。
(*env)->ExceptionClear(env);
抛出异常
JNI允许本地代码抛出Java异常。可以使用`ThrowNew`函数来抛出一个新的异常。
jclass exceptionClass = (*env)->FindClass(env, "java/lang/RuntimeException"); if (exceptionClass != NULL) { (*env)->ThrowNew(env, exceptionClass, "An error occurred in native code"); }
示例代码:JNI中的异常处理
以下是一个简单的JNI函数示例,展示了如何检查和处理Java异常:
JNIEXPORT void JNICALL Java_ClassName_nativeMethod(JNIEnv *env, jobject obj) { // ... 执行一些操作 ... // 检查是否有异常发生 if ((*env)->ExceptionOccurred(env)) { // 处理异常 (*env)->ExceptionDescribe(env); // 打印异常信息到标准错误输出 (*env)->ExceptionClear(env); // 清除异常状态 // 可以选择抛出自定义异常或执行其他错误处理逻辑 jclass exceptionClass = (*env)->FindClass(env, "java/lang/RuntimeException"); if (exceptionClass != NULL) { (*env)->ThrowNew(env, exceptionClass, "An error occurred in native code"); } } // ... 继续执行其他操作 ... }
在本地代码中处理异常时需要注意以下几点:
通过JNI提供的异常处理机制,本地代码可以与Java层的异常处理逻辑协同工作,确保程序的健壮性和稳定性。
以下是对第五部分第三小节“多线程与JNI”内容的扩充:
多线程与JNI多线程支持概述
JNI中的多线程模型:在JNI环境下,Java虚拟机(JVM)本身是支持多线程运行的。这意味着可以有多个Java线程同时存在并执行。然而,对于本地代码(C/C++ 等)与JVM之间的交互,在多线程场景下有特殊的规则和要求。 JVM的多线程模型基于操作系统的线程实现,例如在类Unix系统中通常基于pthread库。这种基于底层操作系统线程构建的方式使得JVM能够充分利用多核处理器的性能优势。 线程创建与管理:在JNI环境中创建线程,可以使用多种方式。如果是在Android平台上,可以利用Android提供的线程创建机制,如`pthread_create`函数(遵循POSIX线程标准)。在其他平台或者更通用的场景下,也可以使用C++11中的`std::thread`库来创建线程。 当创建一个新线程时,需要考虑线程的生命周期管理。这包括线程启动时的初始化工作,例如设置线程特定的数据(如果需要),以及在线程结束时进行资源的清理和释放。对于JNI相关的线程,特别要注意与JVM的正确交互,确保不会出现资源泄漏或者未定义的行为。 AttachCurrentThread与DetachCurrentThread
AttachCurrentThread:`AttachCurrentThread`函数是JNI中用于将当前本地线程附着到JVM的关键函数。它的作用是让JVM识别这个本地线程,从而使得该线程能够调用JNI函数。 在调用`AttachCurrentThread`时,需要传递一个指向`JNIEnv`指针的指针(通常为`JNIEnv **`类型)以及一些可选的线程属性参数。JVM会为这个线程分配一个`JNIEnv`实例,并将其地址通过传入的指针返回。 例如,在C代码中可能的调用方式如下:
JavaVM *jvm;
JNIEnv *env;
// 假设jvm已经被正确初始化
(*jvm)->AttachCurrentThread(jvm, &env, NULL);
这个函数必须在本地线程首次调用任何JNI函数之前被调用,否则会导致未定义的行为。DetachCurrentThread: 当本地线程完成与JVM相关的操作后,必须调用`DetachCurrentThread`函数将线程从JVM中分离。这是因为JVM会为附着线程维护一些内部状态信息,如果不进行分离,可能会导致内存泄漏等问题。 分离线程的操作相对简单,只需要传入对应的`JavaVM`指针即可。例如:
(*jvm)->DetachCurrentThread(jvm);
一旦线程被分离,就不能再在该线程中调用JNI函数,除非再次调用`AttachCurrentThread`重新附着。
线程局部存储与JNIEnv
同步与线程安全
示例代码
#include <jni.h> #include <pthread.h> JavaVM *jvm; void *nativeThreadFunc(void *arg) { JNIEnv *env; // 附着线程到JVM (*jvm)->AttachCurrentThread(jvm, &env, NULL); // 这里可以进行JNI相关的操作,例如调用Java方法或者访问Java对象 // 假设已经有一个Java类的引用jclass cls和一个方法ID mid jobject obj = // 获取Java对象的方式 (*env)->CallVoidMethod(env, obj, mid); // 分离线程 (*jvm)->DetachCurrentThread(jvm); return NULL; } JNIEXPORT void JNICALL Java_ClassName_nativeMethod(JNIEnv *env, jobject thiz) { // 获取JavaVM指针 (*env)->GetJavaVM(env, &jvm); pthread_t thread; // 创建线程 pthread_create(&thread, NULL, nativeThreadFunc, NULL); // 等待线程结束(这里可以根据实际需求决定是否等待) pthread_join(thread, NULL); }本博客文章除特别声明,全部都是原创!
原创文章版权归过往记忆大数据(过往记忆)所有,未经许可不得转载。
本文链接: 【《深入理解 JNI》JNI 高级特性】(https://www.iteblog.com/archives/10224.html)