异常是怎么被处理的?这题的答案不在源码里面。 (下)

简介: 异常是怎么被处理的?这题的答案不在源码里面。 (下)

怎么样,发现了没?就问你神不神奇?

在源码中,只在 finally 代码块出现过一次的输出语句,在字节码中出现了三次。

finally 代码块中的代码被复制了两份,分别放到了 try 和 catch 语句的后面。再配合异常表使用,就能达到 finally 语句一定会被执行的效果。

以后再也不怕面试官问你为什么 finally 一定会执行了。

虽然应该也没有面试官会问这样无聊的问题。

问起来了,就从字节码的角度给他分析一波。

当然了,如果你非要给我抬个杠,聊聊 System.exit 的情况,就没多大意义了。

最后,关于 finally,再讨论一下这个场景:

public class MainTest {
    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } finally {
            System.out.println("final");
        }
    }
}

这个场景下,没啥说的, try 里面抛出异常,触发 finally 的输出语句,然后接着被抛出去,打印在控制台:

image.png

如果我在 finally 里面加一个 return 呢?

可以看到,运行结果里面异常都没有被抛出来:


image.png


为什么呢?

答案就藏在字节码里面:


image.png

其实已经一目了然了。

右边的 finally 里面有 return,并没有 athrow 指令,所以异常根本就没有抛出去。

这也是为什么建议大家不要在 finally 语句里面写 return 的原因之一。


冷知识


再给大家补充一个关于异常的冷知识吧。

image.png

还是上面这个截图。你有没有觉得有一丝丝的奇怪?

夜深人静的时候,你有没有想过这样的一个问题:

程序里面并没有打印日志的地方,那么控制台的日子是谁通过什么地方打印出来的呢?

是谁干的?

这个问题很好回答,猜也能猜到,是 JVM 帮我们干的。

什么地方?

这个问题的答案,藏在源码的这个地方,我给你打个断点跑一下,当然我建议你也打个断点跑一下:

java.lang.ThreadGroup#uncaughtException


image.png

image.png

而在这个地方打上断点,根据调用堆栈顺藤摸瓜可以找到这个地方:

java.lang.Thread#dispatchUncaughtException


image.png

看方法上的注释:

This method is intended to be called only by the JVM.

翻译过来就是:这个方法只能由 JVM 来调用。

既然源码里面都这样说了,我们可以去找找对应的源码嘛。

https://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/5b9a416a5632/src/share/vm/runtime/thread.cpp

在 openJdk 的 thread.cpp 源码里面确实是找到了该方法被调用的地方:


image.png

而且这个方法还有个有意思的用法。

看下面的程序和输出结果:

image.png

我们可以自定义当前线程的 UncaughtExceptionHandler,在里面做一些兜底的操作。

有没有品出来一丝丝全局异常处理机制的味道?

好了,再来最后一个问题:

image.png

我都这样问了,那么答案肯定是不一定的。

你就想想,发挥你的小脑袋使劲的想,啥情况下 try 里面的代码抛出了异常,外面的 catch 不会捕捉到?

来,看图:

image.png

没想到吧?

这样处理一下,外面的 catch 就捕捉不到异常了。

是不是很想打我。

别慌,上面这样套娃多没意思啊。

你再看看我这份代码:

public class MainTest {
    public static void main(String[] args) {
        try {
            ExecutorService threadPool = Executors.newFixedThreadPool(1);
            threadPool.submit(()->{
               int a=1/0;
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

你直接拿去执行,控制台不会有任何的输出。

来看动图:

image.png

是不是很神奇?

不要慌,还有更绝的。

把上面的代码从 threadPool.submit 修改为 threadPool.execute 就会有异常信息打印出来了:

image.png

但是你仔细看,你会发现,异常信息虽然打印出来了,但是也不是因为有 catch 代码块的存在。

具体是为啥呢?

参见这篇文章,我之前详细讲过的:《关于多线程中抛异常的这个面试题我再说最后一次!》


最后说一句


好了,看到了这里安排个点赞吧。感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。

目录
相关文章
|
8月前
|
存储 前端开发 JavaScript
【面试题】Promise只会概念远远不够,还需这17道题目巩固!
【面试题】Promise只会概念远远不够,还需这17道题目巩固!
|
4月前
|
存储
关于递归处理,应该怎么处理,思路是什么?
本文讨论了如何使用递归处理将嵌套对象中的所有名称(name)属性转换为标签(label)属性的问题,提供了详细的递归函数实现思路和代码示例。
33 0
关于递归处理,应该怎么处理,思路是什么?
|
7月前
|
存储 算法 数据挖掘
【模拟面试问答】力扣165题:比较版本号(逐个比较与双指针法详解及模拟面试问答)
【模拟面试问答】力扣165题:比较版本号(逐个比较与双指针法详解及模拟面试问答)
|
机器学习/深度学习 算法 测试技术
C++动态规划算法的应用:得到 K 个半回文串的最少修改次数 原理源码测试用例
C++动态规划算法的应用:得到 K 个半回文串的最少修改次数 原理源码测试用例
|
编译器 C语言 C++
C语言数组越界造成的死循环例子,当你得到了这个意想不到的结果的时候,你肯定不知道为什么,看你还敢不敢越界访问数组了
C语言数组越界造成的死循环例子,当你得到了这个意想不到的结果的时候,你肯定不知道为什么,看你还敢不敢越界访问数组了
126 0
|
存储 测试技术
删除链表中重复的结点(手把手带你理解思路,从错误代码带你逐步完善代码,非常实用!)
删除链表中重复的结点(手把手带你理解思路,从错误代码带你逐步完善代码,非常实用!)
196 0
删除链表中重复的结点(手把手带你理解思路,从错误代码带你逐步完善代码,非常实用!)
|
IDE 程序员 开发工具
一道面试题的最终答案
一道面试题的最终答案
123 1
|
Java
java学习第四天笔记-流程控制语句-分支结构81-判断和循环次数-回文数leetcode
java学习第四天笔记-流程控制语句-分支结构81-判断和循环次数-回文数leetcode
99 0
java学习第四天笔记-流程控制语句-分支结构81-判断和循环次数-回文数leetcode
|
Java
JDK打印的疑问:CUPSPrinter有何用处?
JDK打印的疑问:CUPSPrinter有何用处?
99 0