有关JAVA虚拟机本地异常机制的思考
By 姜江<jznsmail@tom.com>
http://blog.csdn.net/jznsmail

在JAVA语言中,如果需要自己实现一个异常函数可以通过在函数后通过throws来指定抛出何种类型的异常,如果是类则可以通过继承Exception类来抛出指定类型的异常。下面以自定义异常函数为列子:
public class ExceptionTest
{
 public void Exception1() throws Exception
 {
  throw new Exception();
 }
 
 public static void main(String args[])
 {
  ExceptionTest exp = new ExceptionTest();
  exp.Exception1(); /* Error */
 }
}
上面代码是一个抛出异常的函数,在main函数中会调用到该异常函数。不过上面的代码有点问题,在JAVA语言中如果定义了一个异常函数,则调用者应该负责捕获该函数返回的异常,因此需要在exp.Exception1()位置加入try{}catch语句。
try
{
 exp.Exception1();
}
catch(Exception e)
{
 System.out.println("Catch Exception1 Exception...");
}

这是属于JAVA语言中异常处理的标准情况,那么对于本地代码的异常处理机制会是怎样的?
编写如下代码:
public class ExceptionTest
{
 static
 {
  System.loadLibrary("ExceptionTest");
 }
 public native void Except1() throws Exception;
 public native void Except2();
 public void Except3()
 {
  System.out.println("################Java Except3################");
 }
 
 public static void main(String args[])
 {
  ExceptionTest exp = new ExceptionTest();
  try
  {
   exp.Except1();
  }
  catch(Exception e)
  {
   System.out.println("############Catch Exception 1################");
  }
  
  exp.Except2();
  
   exp.Except3();
  
   System.out.println("Successfull Exit...");
 }
}

这段代码定义了两个函数Exception1(),Exception2(),不过只有Exception1()函数声明了会抛出一个Exception异常,而Exception2()函数并没有直接声明,但是我会使得Exception2()函数也抛出一个Exception异常,看看最后的结果是怎样的。
通过JNI接口我可以定义一个动态连接库文件ExceptionTest.DLL(Linux下是.so文件):
#include <stdio.h>
#include <jni.h>
#include "ExceptionTest.h"

JNIEXPORT void JNICALL Java_ExceptionTest_Except1
  (JNIEnv *env, jclass)
{
 //jthrowable exp;
 env->ThrowNew(env->FindClass("java/lang/Exception"), "####Except1:This is C Code.####/n");
 printf("###############Native Fun Except1#################/n");
}

JNIEXPORT void JNICALL Java_ExceptionTest_Except2
  (JNIEnv *env, jclass)
{
 env->ThrowNew(env->FindClass("java/lang/Exception"), "####Except2:This is C Code.####/n");
 printf("###############Native Fun Except2#################/n");
}

在函数定义中Exception2实际上也抛出了一个Exception的异常,JAVA代码调用本地代码的运行结果是:
###############Native Fun Except1#################
############Catch Exception 1################
###############Native Fun Except2#################
Exception in thread "main" java.lang.Exception: ####Except2:This is C Code.####

        at ExceptionTest.Except2(Native Method)
        at ExceptionTest.main(ExceptionTest.java:34)

结果显示Except2()函数抛出了一个异常,但是我们并没有捕获,因此程序异常退出了。
如果我们在JAVA代码中对Exception2()进行捕获结果会怎样?在Exception2()函数位置使用try{}catch语句后运行结果是:
###############Native Fun Except1#################
############Catch Exception 1################
###############Native Fun Except2#################
############Catch Exception 2################
################Java Except3################
Successfull Exit...

这个列子证明Exception2()函数抛出的异常在我们的JAVA代码中被捕获了。
因此,在JAVA VM设计中,KNI层的代码会抛出一系列的异常,但是有些JAVA库函数中声明了native函数将会throws某种类型的异常,而有些却没有明确声明,起始这个对于KVM的实现者来说并不重要,实现者只需要保证与机器相关代码的正确性,并且返回JAVA规范所规定的异常即可。至于如何和何时捕获异常那是调用者的事情,即使用库函数者的事情了。
伪代码:
public class Graphics
{
 ...
 public native void DrawChar(int x, int y, char c);
 ...
}
以上函数并没有显示的声明该函数会返回一个异常。
KNI层的伪代码实现如下:
Java_javax_microedition_lcdui_Graphics_drawChar()
{
 ...
 if(!checkAnchor(anchor, VCENTER))
 {
  KNI_ThrowNew(midpIllegalArgumentException, NULL);
 }
 else
 {
  ...
 }
 ...
}
通过上面的KVM实现的伪代码可以看出,JAVA库函数中DrawChar并没有指定该函数会抛出任何异常信息,但是在实际的本地代码的实现中却抛出了IllegalArgumentException异常。因此在KVM的实现过程中并不能仅仅通过KNI接口函数定义中是否明确指示函数抛出某种类型的异常,来判断底层移植接口是否应该抛出异常信息。具体的异常抛出应该参考J2ME的实现规范。

由此可知,盲目的相信已有代码的正确性并不是一件好事,正如今天老大说的,应该抱着怀疑的态度来看待任何“小”问题:) 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐