野指针
野指针简介
野指针指向的是一个无效的地址。
- 该地址如果不可读不可写,马上会
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);} |
---|
总结
- 当调用一个JNI函数后,必须先检查、处理、清除异常后再做其它 JNI 函数调用,否则会产生不可预知的结果;
- 一旦发生异常,立即返回,让调用者处理这个异常。或 调用
ExceptionClear
清除异常,然后执行自己的异常处理代码; - 异常处理的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);