異常簡介
異常,顯而意見就是程序在運行期間沒有按照正常的程序邏輯執行,在執行過程當中出現了某種錯誤,導致程序崩潰。在Java中異常分爲運行時異常(RuntimeException)和編譯時異常,在程序中有可能運行期間發生異常的邏輯我們會用try…catch…來處理,如果沒有處理的話,在運行期間發生異常就會導致程序奔潰。而編譯時異常是在編譯期間就必須處理的。本章主要介紹運行時異常。
示例1:
// 運行時異常
public static void exceptionCallback() {
int a = 20 / 0;
System.out.println("--->" + a);
}
示例2:
// 編譯期間異常
public static void testException() throws Exception {
// ...
System.out.println("testException() invoked!");
}
public static void main(String[] args) {
exceptionCallback();
try {
testException();
} catch (Exception e) {
e.printStackTrace();
}
// ....
}
在示例2中,testException方法聲明時顯示拋出了一個java.lang.Exception異常,所以在程序調用的地方必須用try...catch處理。
大家都知道,如果示例2中main方法執行到調用exceptionCallback方法時,方法第一行有一個除0的操作,因此該方法會拋出java.lang.ArithmeticException
數學異常,而在main方法調用的時候並沒有處理這個函數在運行時可能會發生的異常,所以會導致程序立即結束,而後面的代碼try{testException();}catch(Exception
e) {e.printStackTrace();}
都不會被執行。運行示例2程序的你會看到下面的結果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.study.jnilearn.JNIException.exceptionCallback(JNIException.java:8)
at com.study.jnilearn.JNIException.main(JNIException.java:22)
我們改進一下上面這個程序:
public static void main(String[] args) {
try {
exceptionCallback();
} catch (Exception e) {
e.printStackTrace();
}
try {
testException();
} catch (Exception e) {
e.printStackTrace();
}
}
這時我們運行程序,調用exceptionCallback
方法時會引發java.lang.ArithmeticException:
/ by zero
異常,由於我們用try…catch塊顯示處理了異常,所以程序會繼續往下執行,調用testException()函數,打印testException()
invoked!
。運行結果如下所示:
java.lang.ArithmeticException: / by zero
at com.study.jnilearn.JNIException.exceptionCallback(JNIException.java:8)
at com.study.jnilearn.JNIException.main(JNIException.java:24)
testException() invoked!
Java與JNI處理異常的區別
下面來小結一下:
1、在Java中如果覺得某段邏輯可能會引發異常,用try…catch機制來捕獲並處理異常即可
2、如果在Java中發生運行時異常,沒有使用try…catch來捕獲,會導致程序直接奔潰退出,後續的代碼都不會被執行 3、編譯時異常,是在方法聲明時顯示用throw聲明瞭某一個異常,編譯器要求在調用的時候必須顯示捕獲處理 public static void testException() throws Exception
{}
上面這幾點,寫過Java的朋友都知道,而且很簡單,但我爲什麼還要拿出來說呢,其實我想重點說明的是,在JNI中發生的異常和Java完全不一樣。我們在寫JNI程序的時候,JNI沒有像Java一樣有try…catch…final這樣的異常處理機制,面且在本地代碼中調用某個JNI接口時如果發生了異常,後續的本地代碼不會立即停止執行,而會繼續往下執行後面的代碼。
異常處理示例
示例3: 這個例子在main中調用了doit本地方法,在本地方法中會回調exceptionCallback
方法,該方法中會引發一個除0的運行時異常java.lang.ArithmeticException
,我們通過這個示例來學習在JNI中如何來正確處理這種異常。
package com.study.jnilearn;
public class JNIException {
public static native void doit();
public static void exceptionCallback() {
int a = 20 / 0;
System.out.println("--->" + a);
}
public static void normalCallback() {
System.out.println("In Java: invoke normalCallback.");
}
public static void main(String[] args) {
doit();
}
static {
System.loadLibrary("JNIException");
}
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_JNIException */
#ifndef _Included_com_study_jnilearn_JNIException
#define _Included_com_study_jnilearn_JNIException
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_study_jnilearn_JNIException
* Method: doit
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_study_jnilearn_JNIException_doit
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
// JNIException.c
#include "com_study_jnilearn_JNIException.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_com_study_jnilearn_JNIException_doit(JNIEnv *env, jclass cls) {
jthrowable exc = NULL;
jmethodID mid = (*env)->GetStaticMethodID(env,cls,"exceptionCallback","()V");
if (mid != NULL) {
(*env)->CallStaticVoidMethod(env,cls,mid);
}
printf("In C: Java_com_study_jnilearn_JNIException_doit-->called!!!!");
if ((*env)->ExceptionCheck(env)) { // 檢查JNI調用是否有引發異常
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env); // 清除引發的異常,在Java層不會打印異常的堆棧信息
(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"JNI拋出的異常!");
//return;
}
mid = (*env)->GetStaticMethodID(env,cls,"normalCallback","()V");
if (mid != NULL) {
(*env)->CallStaticVoidMethod(env,cls,mid);
}
}
程序運行結果如下:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.study.jnilearn.JNIException.exceptionCallback(JNIException.java:8)
at com.study.jnilearn.JNIException.doit(Native Method)
at com.study.jnilearn.JNIException.main(JNIException.java:17)
Exception in thread "main" java.lang.Exception: JNI拋出的異常!
at com.study.jnilearn.JNIException.doit(Native Method)
In Java: invoke normalCallback.
at com.study.jnilearn.JNIException.main(JNIException.java:17)
In C: Java_com_study_jnilearn_JNIException_doit-->called!!!!
在Main方法中調用doit本地方法後,程序的控制權即交給了JNI,在doit的本地方法中回調exceptionCallback方法,引發了一個java.lang.ArithmeticException
異常,但本地接口並不會馬上退出,而是會繼續執行後面的代碼,所以我們在調用完一個任何一個JNI接口之後,必須要做的一件事情就是檢查這次JNI調用是否發生了異常,如果發生了異常不處理,而繼續讓程序執行後面的邏輯,將會產生不可預知的後果。在本例中,我們調用了JNI的ExceptionCheck
函數檢查最近一次JNi調用是否發生了異常,如果有異常這個函數返回JNI_TRUE,否則返回JNI_FALSE。當檢測到異常時,我們調用ExceptionDescribe
函數打印這個異常的堆棧信息,然後再調用ExceptionClear
函數清除異常堆棧信息的緩衝區(如果不清除,後面調用ThrowNew拋出的異常堆棧信息會覆蓋前面的異常信息),最後調用ThrowNew
函數手動拋出一個java.lang.Exception異常。但在JNI中拋出未捕獲的異常與Java的異常處理機制不一樣,在JNI中並不會立即終止本地方法的執行,而是繼續執行後面的代碼。這種情況需要我們手動來處理。在例中的38行,如果你不用return馬上退出方法的話,37行ThrowNew後面的代碼依然會繼續執行,如程序運行的結果一樣,仍然會回調normalCallback
方法,打印出:invoke
normalCallback.
異常檢查JNI還提供了另外一個接口,ExceptionOccurred
,如果檢測有異常發生時,該函數會返回一個指向當前異常的引用。作用和ExceptionCheck
一樣,兩者的區別在於返回值不一樣。我們改造一下上面的例子:
// ....
jthrowable exc = NULL;
exc = (*env)->ExceptionOccurred(env); // 返回一個指向當前異常對象的引用
if (exc) {
(*env)->ExceptionDescribe(env); // 打印Java層拋出的異常堆棧信息
(*env)->ExceptionClear(env); // 清除異常信息
// 拋出我們自己的異常處理
jclass newExcCls;
newExcCls = (*env)->FindClass(env,"java/lang/Exception");
if (newExcCls == NULL) {
return;
}
(*env)->ThrowNew(env, newExcCls, "throw from C Code.");
}
// ....
寫一個拋出異常的工具類
當需要拋出自己的異常處理邏輯時,需要二步,調用FindClass找到異常處理類,然後調用ThrowNew拋出一個異常。爲了簡化操作步聚,我們寫一個工具函數,根據一個異常類名專門用來生成一個指定名字的異常:
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);
}
總結
1、當調用一個JNI函數後,必須先檢查、處理、清除異常後再做其它 JNI 函數調用,否則會產生不可預知的結果。
2、一旦發生異常,立即返回,讓調用者處理這個異常。或 調用 ExceptionClear 清除異常,然後執行自己的異常處理代碼。
3、異常處理的相關JNI函數總結:
1> ExceptionCheck:檢查是否發生了異常,若有異常返回JNI_TRUE,否則返回JNI_FALSE
2> ExceptionOccurred:檢查是否發生了異常,若用異常返回該異常的引用,否則返回NULL
3> ExceptionDescribe:打印異常的堆棧信息
4> ExceptionClear:清除異常堆棧信息
5> ThrowNew:在當前線程觸發一個異常,並自定義輸出異常信息 jint (JNICALL *ThrowNew) (JNIEnv *env, jclass clazz,
const char *msg);
6> Throw:丟棄一個現有的異常對象,在當前線程觸發一個新的異常 jint (JNICALL *Throw) (JNIEnv *env, jthrowable obj);
7> FatalError:致命異常,用於輸出一個異常信息,並終止當前VM實例(即退出程序) void (JNICALL *FatalError) (JNIEnv *env, const char
*msg);