finally块总是会运行吗?

124

在Java中,是否存在最终不会运行的情况?谢谢。


13
在试图加入某个知名公司时,可能会有人问你这个问题吗? - Tom Hawtin - tackline
@TomHawtin-tackline 你能给它起个名字吗?(天啊,我错过了这篇文章的年代!) - Hele
6
@Hele 我不想泄露答案,但你可以在谷歌上搜索它。 - Tom Hawtin - tackline
短回答:是的,在正常情况下。 - Shark
12个回答

145

来自Sun中文教程

注意:如果JVM在执行try或catch代码时退出,则finally块可能不会执行。类似地,如果执行try或catch代码的线程被中断或终止,则finally块可能不会执行,尽管整个应用程序仍在运行。

我不知道还有其他方式finally块不会执行...


7
@dhiller- 我很确定“power down”已包含在“If the JVM exits...”中了 :-p - Jason Coco
2
@Jason Coco:终止(例如由于断电)并不完全等同于退出;后者是一个更或多或少有组织的过程,最终导致前者。;p - user359996
2
据我所知,如果线程被中断,它不会立即停止。线程中的代码需要检测中断并停止其任务,因此 finally 代码应该运行。 - Bart van Heukelom
2
我猜如果在finally块中抛出异常,那么该块的其余部分将不会被执行。 - Adriaan Koster
那真糟糕! - eiran

71

System.exit 方法关闭虚拟机。

终止当前正在运行的 Java 虚拟机。 参数作为状态码;按照惯例,非零状态码表示异常终止。

此方法调用Runtime类中的exit方法。该方法永远不会正常返回。

    try {
        System.out.println("hello");
        System.exit(0);
    }
    finally {
        System.out.println("bye");
    } // try-finally

在上面的代码中,"bye" 没有被输出。


同时,一个异常应该关闭 Java 虚拟机。 - kaissun
3
如果在执行System.exit(0)时发生异常,那么finally块将会被执行。 - halil

53

只是为了扩充其他人所说的,任何不会导致类似于JVM退出的情况都将包含在finally块中。因此,以下方法:

public static int Stupid() {
  try {
    return 0;
  }
  finally {
    return 1;
  }
}

它会奇怪地编译并返回1。


2
这个问题在几周前让我迷惑了好几个小时。 - nickf
3
从finally块中返回一个值被认为是一个不好的做法。要么只从try块中返回,要么从try/finally块外部返回。大多数IDE都会用警告标记这一点。 - Ran Biron
此外,为什么使用javac -Xlint(就像cc -Wall -Wextra一样)是一个好主意。 - Jason Coco
1
@nickf 我了解你现在不再困惑了。你能详细说明一下为什么返回的是1而不是0的机制吗?我只能猜测最初存储函数返回值的内存(或寄存器)持有0,但在执行finally块时被覆盖了。 - Yaneeve
2
很奇怪,在C#中不允许从finally块返回。 - JMCF125
3
当然可以。他实际上并没有建议在finally块中使用return语句,他只是试图证明即使有return语句,该块中的代码仍将被执行。 - Aquarelle

15

与System.exit相关的是,有些灾难性故障可能导致finally块不能执行。如果JVM完全耗尽内存,则可能会直接退出而不会发生catch或finally。

具体来说,我记得有一个项目我们愚蠢地尝试使用

catch (OutOfMemoryError oome) {
    // do stuff
}

这没起作用是因为JVM没有足够的内存来执行catch块。


当抛出OutOfMemoryError时,通常还有很多内存剩余(以防止GC抖动)。然而,如果您反复捕获它,显然会回到GC抖动状态。 - Tom Hawtin - tackline
我认为不应该捕获未经检查的异常! - Sergii Shevchyk
1
我在我的端上尝试了使用jdk7,但它确实捕获了OutOfMemory错误! - JaskeyLam

10

这个帖子错误引用了Sun教程。

注意: 如果JVM在执行try或catch代码时退出,则finally块可能不会执行。同样,如果执行try或catch代码的线程被中断或终止,即使应用程序整体继续运行,finally块也可能不会执行。

如果您仔细查看Sun教程中的finally块,它并没有说“不会执行”,而是“可能不会执行”,这里是正确的描述:

注意: 如果JVM在执行try或catch代码时退出,则finally块可能不会执行。同样,如果执行try或catch代码的线程被中断或杀死,则finally块可能不会执行,尽管应用程序作为一个整体继续运行。

这种行为的显而易见的原因是,在运行时系统线程中处理调用system.exit(),这可能需要一些时间来关闭jvm,同时线程调度程序可以要求执行finally。因此,finally被设计为始终执行,但如果您正在关闭jvm,则有可能在finally得到执行之前关闭jvm。


10
try { for (;;); } finally { System.err.println("?"); }

在这种情况下,finally语句将不会执行(除非调用了已弃用的Thread.stop方法或等效方法,比如通过工具接口调用)。


此页面声称在调用Thread.stop()时抛出ThreadDeath错误,并且堆栈正常解开。我是否遗漏了一些细节?http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html - spurserh
我认为这里没有陷阱。也许你正在想象一个不存在的陷阱。如果我们放置一个明确的“throw”,那么“finally”块将按预期执行。try { throw new ThreadDeath(); } finally { System.err.println("?"); } - Tom Hawtin - tackline

6

如果在try块内发生死锁/活锁,以下是演示它的代码:

public class DeadLocker {
    private static class SampleRunnable implements Runnable {
        private String threadId;
        private Object lock1;
        private Object lock2;

        public SampleRunnable(String threadId, Object lock1, Object lock2) {
            super();
            this.threadId = threadId;
            this.lock1 = lock1;
            this.lock2 = lock2;
        }

        @Override
        public void run() {
            try {
                synchronized (lock1) {
                    System.out.println(threadId + " inside lock1");
                    Thread.sleep(1000);
                    synchronized (lock2) {
                        System.out.println(threadId + " inside lock2");
                    }
                }
            } catch (Exception e) {
            } finally {
                System.out.println("finally");
            }
        }

    }

    public static void main(String[] args) throws Exception {
        Object ob1 = new Object();
        Object ob2 = new Object();
        Thread t1 = new Thread(new SampleRunnable("t1", ob1, ob2));
        Thread t2 = new Thread(new SampleRunnable("t2", ob2, ob1));
        t1.start();
        t2.start();
    }
}

这段代码将产生以下输出:

t1 inside lock1
t2 inside lock1

而“最终”从未被打印出来。


3
从技术上讲,try块永远不会退出,因此finally块不应该有机会执行。对于无限循环也可以这样说。 - Jeff Mercado

6
如果JVM在执行try或catch代码时退出,则finally块可能不会执行。(source
正常关闭 - 当最后一个非守护线程退出或Runtime.exit()(source)时发生。
当线程退出时,JVM执行正在运行的线程清单,如果剩下的只有守护线程,则启动有序关闭。当JVM停止时,任何剩余的守护线程都将被放弃,finally块不会被执行,堆栈不会被解开,JVM直接退出。应该谨慎使用守护线程,很少有处理活动可以安全地随时放弃而不进行清理。特别是,对于可能执行任何类型的I/O任务的任务来说,使用守护线程是危险的。最好将守护线程保存用于“日常事务”,例如定期从内存缓存中删除过期条目的后台线程。(source
最后一个非守护线程退出示例:
public class TestDaemon {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                while (true) {
                    System.out.println("Is alive");
                    Thread.sleep(10);
                    // throw new RuntimeException();
                }
            } catch (Throwable t) {
                t.printStackTrace();
            } finally {
                System.out.println("This will never be executed.");
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread daemon = new Thread(runnable);
        daemon.setDaemon(true);
        daemon.start();
        Thread.sleep(100);
        // daemon.stop();
        System.out.println("Last non-daemon thread exits.");
    }
}

输出:
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Is alive
Last non-daemon thread exits.
Is alive
Is alive
Is alive
Is alive
Is alive

2
在以下情况下,finally块将不会被执行:
  • try块中调用System.exit(0)时。
  • JVM内存耗尽时。
  • 当您的java进程被任务管理器或控制台强制终止时。
  • try块中出现死锁条件。
  • 由于电源故障而导致机器关闭。
可能还有其他一些边缘情况,finally块也将不会被执行。

1

有两种方法可以停止finally块中的代码执行:
1. 使用System.exit();
2. 如果执行控制不会到达try块。
参见:

public class Main
{
  public static void main (String[]args)
  {
    if(true){
        System.out.println("will exceute");
    }else{
        try{
            System.out.println("result = "+5/0);
        }catch(ArithmeticException e){
          System.out.println("will not exceute");
        }finally{
          System.out.println("will not exceute");  
        }
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接