JNI 异常处理

2019-12-25 19:25:54 浏览数 (1)

野指针

野指针简介

野指针指向的是一个无效的地址。

  • 该地址如果不可读不可写,马上会Crash,内核给进程发送错误的信号SIGSEGV,这时候bug会很快被发现。
  • 如果放问地址可写,而且通过野指针修改了该处的内存,那么很有可能等一段时间才会发送Crash,这时候查看Crash调用的栈,和野指针所在的代码部分,几乎没有任何关联。

避免野指针崩溃的方法

  • 在指针变量定义时候,一定要初始化,特别是在结构体或者类中的成员指针变量
  • 在释放了指针指向的内存后,要把该指针置为NULL(如果在别处,也有指针指向该处的内存这种方式就不好解决)。
  • 想要解决野指针问题,不能靠看代码,使用代码分析工具,而要使用专业的内存检测工具才能发现bug。

异常

异常产生原因

  • 编译时异常:编译器在编译期间,对于受检异常必须进行try...catch或者throws进行处理,否则无法编译通过,比如File,stream,线程操作等;
  • 运行时异常:Java程序在运行期间所产生的异常,RuntimeException及其子类都统称为非受检查异常,例如:NullPointExecrption、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)等;

Java与JNI处理异常区别

  • Java中可以用try...catch机制来捕获并处理异常;
  • 如果在Java中发生运行时异常,没有使用try...catch来捕获,会导致程序直接奔溃退出,后续的代码都不会被执行;
  • 编译时异常,是在方法声明时显示用throw声明了某一个异常,编译器要求在调用的时候必须显示捕获处理;
  • 而在JNI中,由于JNI没有像Java一样有try...catch...final这样的异常处理机制,面且在本地代码中调用某个JNI接口时如果发生了异常,后续的本地代码不会立即停止执行,而会继续往下执行后面的代码;

处理异常的方式

  • ExceptionCheck
  • ExceptionOccurred
ExceptionCheck

调用了JNI的ExceptionCheck函数检查最近一次JNI调用是否发生了异常,如果有异常这个函数返回JNI_TRUE,否则返回JNI_FALSE

//异常捕获 ,检查JNI调用是否有异常 if(env->ExceptionCheck()){ env->ExceptionDescribe(); env->ExceptionClear();//清除引发的异常,在Java层不会打印异常堆栈信息,如果不清除,后面的调用ThrowNew抛出的异常堆栈信息会 //覆盖前面的异常信息 jclass cls_exception = env->FindClass("java/lang/Exception"); env->ThrowNew(cls_exception,"call java static method ndk error"); return; }

123456789

//异常捕获 ,检查JNI调用是否有异常if(env->ExceptionCheck()){    env->ExceptionDescribe();    env->ExceptionClear();//清除引发的异常,在Java层不会打印异常堆栈信息,如果不清除,后面的调用ThrowNew抛出的异常堆栈信息会//覆盖前面的异常信息    jclass cls_exception = env->FindClass("java/lang/Exception");    env->ThrowNew(cls_exception,"call java static method ndk error");    return;}

ExceptionOccurred

异常检查JNI还提供了另外一个接口,ExceptionOccurred,如果检测有异常发生时,该函数会返回一个指向当前异常的引用。作用和ExceptionCheck一样,两者的区别在于返回值不一样。

//异常捕获,第二种方法 if(env->ExceptionOccurred()){ env->ExceptionDescribe(); env->ExceptionClear(); jclass cls_exception = env->FindClass("java/lang/Exception"); env->ThrowNew(cls_exception,"call java static method ndk error"); return; }

12345678

//异常捕获,第二种方法if(env->ExceptionOccurred()){    env->ExceptionDescribe();    env->ExceptionClear();    jclass cls_exception = env->FindClass("java/lang/Exception");    env->ThrowNew(cls_exception,"call java static method ndk error");    return;}

异常抛出工具类

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) { // 查找异常类 jclass cls = (*env)->FindClass(env, name); /* 如果这个异常类没有找到,VM会抛出一个NowClassDefFoundError异常 */ if (cls != NULL) { (*env)->ThrowNew(env, cls, msg); // 抛出指定名字的异常 } /* 释放局部引用 */ (*env)->DeleteLocalRef(env, cls); }

1234567891011

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) {     // 查找异常类     jclass cls = (*env)->FindClass(env, name);     /* 如果这个异常类没有找到,VM会抛出一个NowClassDefFoundError异常 */     if (cls != NULL) {         (*env)->ThrowNew(env, cls, msg); // 抛出指定名字的异常     }     /* 释放局部引用 */     (*env)->DeleteLocalRef(env, cls); }

异常发生后释放资源

在异常发生后,释放资源是一件很重要的事情。下面的例子中,调用 GetStringChars 函数后,如果后面的代码发生异常,要记得调用 ReleaseStringChars 释放资源。

JNIEXPORT void JNICALL Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr) { const jchar *cstr = (*env)->GetStringChars(env, jstr); if (c_str == NULL) { return; } ... if ((*env)->ExceptionCheck(env)) { /* 异常检查 */ (*env)->ReleaseStringChars(env, jstr, cstr); // 发生异常后释放前面所分配的内存 return; } ... /* 正常返回 */ (*env)->ReleaseStringChars(env, jstr, cstr); }

123456789101112131415

JNIEXPORT void JNICALL Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr) {     const jchar *cstr = (*env)->GetStringChars(env, jstr);     if (c_str == NULL) {         return;      }     ...     if ((*env)->ExceptionCheck(env)) { /* 异常检查 */         (*env)->ReleaseStringChars(env, jstr, cstr); // 发生异常后释放前面所分配的内存         return;      }     ...     /* 正常返回 */     (*env)->ReleaseStringChars(env, jstr, cstr);}

总结

  1. 当调用一个JNI函数后,必须先检查、处理、清除异常后再做其它 JNI 函数调用,否则会产生不可预知的结果;
  2. 一旦发生异常,立即返回,让调用者处理这个异常。或 调用 ExceptionClear 清除异常,然后执行自己的异常处理代码;
  3. 异常处理的JNI函数:
    • ExceptionCheck:检查是否发生了异常,若有异常返回JNI_TRUE,否则返回JNI_FALSE;
    • ExceptionOccurred:检查是否发生了异常,若用异常返回该异常的引用,否则返回NULL;
    • ExceptionDescribe:打印异常的堆栈信息;
    • ExceptionClear:清除异常堆栈信息;
    • ThrowNew:在当前线程触发一个异常,并自定义输出异常信息 jint (JNICALL *ThrowNew) (JNIEnv *env, jclass clazz, const char *msg);
    • Throw:丢弃一个现有的异常对象,在当前线程触发一个新的异常 jint (JNICALL *Throw) (JNIEnv *env, jthrowable obj);
    • FatalError:致命异常,用于输出一个异常信息,并终止当前JVM实例(即退出程序; void (JNICALL *FatalError) (JNIEnv *env, const char *msg);

0 人点赞