一、背景
对于很多初学者而言,会想当然地认为 “finally 代码块一定会被执行”,因此我们可以看下面这个案例:
代码语言:javascript复制public class Demo {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
System.out.println(br.readLine());
br.close();
} catch (IOException e) {
// 省略一些代码
} finally {
System.out.println("Exiting the program");
}
}
}
问题是:该段代码 finally 的代码块一定会被执行吗?为什么?
二、分析
通常实际编码时,捕获异常后会记录日志或者将异常抛出等,此时 finally 代码块一般肯定会被执行到。
那么如何才能不执行finally呢?
于是我们想到,如果让虚拟机退出,问题不就解决了吗? (就是这么暴力)
因此填充代码:
代码语言:javascript复制public class Demo {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
System.out.println(br.readLine());
br.close();
} catch (IOException e) {
System.exit(2);
} finally {
System.out.println("Exiting the program");
}
}
}
如果捕获到 IO异常,则会执行 虚拟机退出指令,则不会执行finally 代码块。
System#exit 的源码如下:
代码语言:javascript复制 /**
* Terminates the currently running Java Virtual Machine. The
* argument serves as a status code; by convention, a nonzero status
* code indicates abnormal termination.
*
* This method calls the exit method in class
* Runtime. This method never returns normally.
*
* The call System.exit(n) is effectively equivalent to
* the call:
* * Runtime.getRuntime().exit(n)
*
*
* @param status exit status.
* @throws SecurityException
* if a security manager exists and its checkExit
* method doesn't allow exit with the specified status.
* @see java.lang.Runtime#exit(int)
*/
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
通过注释我们可以了解到, 当 status 为 非0 时,表示异常退出。
底层调用到 Runtime#exit:
代码语言:javascript复制 /**
* Terminates the currently running Java virtual machine by initiating its
* shutdown sequence. This method never returns normally. The argument
* serves as a status code; by convention, a nonzero status code indicates
* abnormal termination.
*
* The virtual machine's shutdown sequence consists of two phases. In
* the first phase all registered {@link #addShutdownHook shutdown hooks},
* if any, are started in some unspecified order and allowed to run
* concurrently until they finish. In the second phase all uninvoked
* finalizers are run if {@link #runFinalizersOnExit finalization-on-exit}
* has been enabled. Once this is done the virtual machine {@link #halt
* halts}.
*
* If this method is invoked after the virtual machine has begun its
* shutdown sequence then if shutdown hooks are being run this method will
* block indefinitely. If shutdown hooks have already been run and on-exit
* finalization has been enabled then this method halts the virtual machine
* with the given status code if the status is nonzero; otherwise, it
* blocks indefinitely.
*
* The {@link System#exit(int) System.exit} method is the
* conventional and convenient means of invoking this method.
*
* @param status
* Termination status. By convention, a nonzero status code
* indicates abnormal termination.
*
* @throws SecurityException
* If a security manager is present and its {@link
* SecurityManager#checkExit checkExit} method does not permit
* exiting with the specified status
*
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkExit(int)
* @see #addShutdownHook
* @see #removeShutdownHook
* @see #runFinalizersOnExit
* @see #halt(int)
*/
public void exit(int status) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExit(status);
}
Shutdown.exit(status);
}
三、延伸
同样的问题,请看下面代码片段:
代码语言:javascript复制public class Demo {
public static void main(String[] args) {
// 一些代码
try {
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
System.out.println(br.readLine());
br.close();
} catch (IOException e) {
System.exit(2);
} finally {
System.out.println("Exiting the program");
}
}
}
问题是:如果try 代码块部分发生IO异常,是否一定不会执行到 finally 代码块呢?
what? 上面不是说不会执行吗?
我们再仔细看上面给出的 Runtime#exit 源码,可以发现,如果 SecurityManager 不为 null ,则 会进行安全检查。
代码语言:javascript复制public void exit(int status) {
// 如果有securityManager , 则调用 checkExit函数
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExit(status);
}
// 检查通过后退出
Shutdown.exit(status);
}
安全检查通过才会执行 Shutdown#exit 执行最终的虚拟机退出。
因此如果我们可以修改 SecurityManager 如果检查退出时抛出异常,那么在 执行 System.exit(2) 时就会发生异常,最终依然会执行到 finally代码块。
代码语言:javascript复制public class Demo {
public static void main(String[] args) {
// 修改 SecurityManager
System.setSecurityManager(new SecurityManager() {
@Override
public void checkExit(int status) {
throw new SecurityException("不允许退出");
}
});
try {
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
System.out.println(br.readLine());
br.close();
} catch (IOException e) {
System.exit(2);
} finally {
System.out.println("Exiting the program");
}
}
}