论坛首页 Java企业应用论坛

try catch finally的递归调用

浏览 32476 次
精华帖 (10) :: 良好帖 (0) :: 新手帖 (5) :: 隐藏帖 (1)
作者 正文
   发表时间:2011-07-21  
非常有意思,如果我把catch (Throwable e) 改成 catch(StackOverflowError e)的话,一切就全部正常了,snap_finally始终为0。

看来还是和JVM的异常匹配机制有关系。具体原因还要进一步分析。
0 请登录后投票
   发表时间:2011-07-21  
的确是的,这个异常换成原始类型就正常了,而且该打的catch也能打出来
0 请登录后投票
   发表时间:2011-07-21   最后修改:2011-07-21
终于找到原因了。

原因是在异常发生的时候,类Throwable没有被加载,所以导致异常类匹配失败。
在调用foo之前,执行一个new Throwable()确保类Throwable在foo调用之前加载就一切正常了。

注:
1. 尽管Throwable出现在代码中,但实际上它只出现在异常表中,所以代码所在的类本身并没有对Throwable的引用,类Throwable自然也不会在代码所在的类加载时加载。
2. 另外由于StackOverflowError的创建是在JVM中直接分配内存创建的,因而也没有加载类Throwable。
9 请登录后投票
   发表时间:2011-07-21  
看来还是栈耗尽导致的throw(catch)失败,但感觉还是没完全想透:
1.为何catch开始会失败
2.为何catch在经过几次finally之后能成功
3.catch失败为何还能成功跳转到finally,是不是最初的几次catch走了catch any这条路从而虽然匹配失败,但是还是成功上抛了异常
4.这种vm的异常控制流怎么介入到app的代码
0 请登录后投票
   发表时间:2011-07-21  
ppgunjack 写道
看来还是栈耗尽导致的throw(catch)失败,但感觉还是没完全想透:
1.为何catch开始会失败

这时因为异常匹配失败的原因。因为产生了StackOverflowError异常,但是由于Throwable加载不成功,所以JVM认为异常匹配不成功,就走缺省的异常处理(就是到finally块,然后rethrow)。
引用
2.为何catch在经过几次finally之后能成功

因为栈返回几次后,类Throwable就能够加载成功,所以这时候就进入正常的try/catch程序。
引用
3.catch失败为何还能成功跳转到finally,是不是最初的几次catch走了catch any这条路从而虽然匹配失败,但是还是成功上抛了异常

是的,如果有finally handler的话,一定存在catch any的异常处理。
引用
4.这种vm的异常控制流怎么介入到app的代码

这时JVM自身的机制吧,不是很清楚你要问什么。
0 请登录后投票
   发表时间:2011-07-21   最后修改:2011-07-21
另外,如果snap_finally >= 2的话,那么第一次在finally handler中处理的异常就是foo递归调用的时候的异常,而后面处理的异常,包括在catch handler中处理的异常就是加载Throwable产生的异常了。

要查看finally handler中的异常,我只想到一个办法,就是classfileanalyzer反汇编代码,然后修改代码,将异常存到变量中,然后用jasmin编译。原代码如下:
		try {   
			tryi++;   
			stack++;
			foo();   
			never++;
		} catch (Throwable e) {   
			catchi++; 
			exception = e;
			snap_try=tryi;   
			snap_finally=finallyi;   
			snap_stack=stack;   
			snap_never=never;
		} finally {   
			if (catchi == 0) {
				save_exs[count++] = null;  // null 将用异常替换
			}
			finallyi++;   
			stack--;
		}   

反汇编后finally handler代码如下(用的是kopi的反汇编工具):
        label482:		// finally 异常 handler 开始
        	@astore		417		// 保持异常到局部变量417
        	@getstatic	int x3.catchi
        	@ifne		label493
        @line	123
        	@getstatic	java.lang.Object[] x3.save_exs
        	@getstatic	int x3.count
        	@dup		
        	@const		1
        	@iadd		
        	@putstatic	int x3.count
        	@aconst_null	// 替换成 @aload 417 就可以得到异常变量了
        	@aastore	// 保持异常到数组中
        @line	125
        label493:
        	@getstatic	int x3.finallyi
        	@const		1
        	@iadd		
        	@putstatic	int x3.finallyi
        @line	126
        	@getstatic	int x3.stack
        	@const		1
        	@isub		
        	@putstatic	int x3.stack
        	@aload		417		// 从局部变量417中load异常到栈
        	@athrow				// 重新抛出异常

把@aconst_null这一行替换成@aload 417就可以得到finally handler中的异常变量了。
经过测试发现,如果snap_finally >= 1,那么finally handler中最后一个处理的异常正好是catch handler中处理的异常。也就是说,这个异常在finally handler中往上一层抛,然后就在上一层的catch handler中获得处理了。
0 请登录后投票
   发表时间:2011-07-21   最后修改:2011-07-22
现在我也觉得最后catch成功截获的异常实际就是来自最初下层catch匹配不成功,而pending住的异常,在finally执行完后栈帧释放,回退到上层catch块重复匹配最后成功

但是对整个流程特别是最早的流程还是模糊
下班前浏览了下vm实现,说得不定准确,jvm应该是在解释器进行字节码转换,在函数调用建栈帧时就探测栈是否触顶来决定是否生成error对象,这个实现实际应该是一个游离在应用程序逻辑所对应的字节码之外的vm添加部分。

app抛出异常然后改变程序流向,不断上溯寻找handler,这个实现不难理解。因为异常源和应用代码有清晰的结合部位(即函数调用),即使是申请内存导致的系统异常也都是能找到具体调用部位,这是异常代码改变调用者代码流的注入点,函数调用代码将异常源和app代码结合在一起,使得异常代码能不断访问查找调用者烦人handler,甚至将调用者退栈

我不太能想清楚对于堆栈溢出这种系统级异常如何加入到app的程序控制流中,因为应用的代码和这个异常判定的代码没明显结合部位,同样比较特殊的还用除0异常。这两种异常都不是调用的函数产生异常,而是调用本身导致的异常,比较绕口
在c++中对于这类系统级的异常处理行为没有明确规定,c++只要求throw的能catch住就行,对于退栈溢出、非法区域访问、除0这类异常是否应该测试到或者抛出都不做规定
c++中遇到这种系统异常应该是通过中断或者陷阱来强行介入代码控制流,这与普通的软件级异常处理逻辑不一样,实际软件级异常的实现c++和java很像,都是发生异常的代码处发起当前代码偏移和起止点比较,然后跳转到handler,但是java怎么处理系统级异常就不知道了
0 请登录后投票
   发表时间:2011-07-22  
ppgunjack 写道
我不太能想清楚对于堆栈溢出这种系统级异常如何加入到app的程序控制流中,因为应用的代码和这个异常判定的代码没明显结合部位,同样比较特殊的还用除0异常。这两种异常都不是调用的函数产生异常,而是调用本身导致的异常,比较绕口
在c++中对于这类系统级的异常处理行为没有明确规定,c++只要求throw的能catch住就行,对于退栈溢出、非法区域访问、除0这类异常是否应该测试到或者抛出都不做规定
c++中遇到这种系统异常应该是通过中断或者陷阱来强行介入代码控制流,这与普通的软件级异常处理逻辑不一样,实际软件级异常的实现c++和java很像,都是发生异常的代码处发起当前代码偏移和起止点比较,然后跳转到handler,但是java怎么处理系统级异常就不知道了

我想是这样的,对于除0这种系统异常,那么vm能够捕获相应的异常(*nix系统中的信号,windows系统中的结构化异常),然后vm查看当前运行的是哪个方法的哪一行代码(不管是解释还是编译方式,vm应该都能获得这个信息),然后在frame中寻找handler就可以了。
至于象栈溢出,应该是vm在方法入口前进行检查的,因为java的栈完全是vm自己维护的数据结构,所以处理方法应该也没什么特别的。
0 请登录后投票
   发表时间:2011-07-22  
由于在snap_finally >= 2的时候看到的异常是类加载的异常,所以我修改了ClassLoader的代码让它记录在出现StackOverflowError的时候是什么类在加载,检查结果证实确实是java.lang.Throwable类。
这是异常:
java.lang.StackOverflowError
        at java.lang.Math.min(Math.java:863)
        at java.util.Arrays.copyOf(Arrays.java:2883)
        at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.
java:100)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390
)
        at java.lang.StringBuilder.append(StringBuilder.java:119)
        at sun.jkernel.DownloadManager.getBootClassPathEntryForClass(DownloadMan
ager.java:891)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:293)
        at java.lang.ClassLoader.loadClass(ClassLoader.java)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
        at x3.foo(x3.java:120)
        at x3.foo(x3.java:120)
        at x3.foo(x3.java:120)
        at x3.foo(x3.java:120)
        at x3.foo(x3.java:120)

这是类加载顺序:
Fail class name: java.lang.Throwable
java.lang.System
java.nio.charset.Charset
java.lang.String
x3
java.lang.Object
java.lang.Throwable
java.lang.Throwable
java.lang.Throwable
java.lang.StringBuffer
java.io.PrintStream
java.lang.ClassNotFoundException
java.lang.NoClassDefFoundError
java.lang.Class
java.lang.ClassLoader
java.lang.reflect.Method
我们可以看到java.lang.Throwable被加载了三次,前两次失败,最后一次成功。
0 请登录后投票
   发表时间:2012-05-05  
xijieqjx 写道
我运行一整天了,还没抛错,笔记本滚烫的,不解?

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics