一、背景
《阿里巴巴Java开发手册》 详解专栏的第 16 节对详细讲述了 JVM的退出时机,给出了几个案例帮助大家理解这个知识点。
该小节给出了一个思考题
public class Demo { public static void main(String[] args) { // 省略一些代码 (第 1 处) try { BufferedReader br = new BufferedReader(new FileReader("file.txt")); System.out.println(br.readLine()); br.close(); } catch (Exception e) { System.exit(2); } finally { System.out.println("Exiting the program"); } } }
如果 try 代码块发生异常,如何在第 1 处代码添加几行代码,使得 finally 代码可以被执行到?
大家如果对虚拟机的退出时机感兴趣,可以购买专栏学习,下文将对该课后答案进行解读。
二、解答
2.1 分析
看到这个问题很多人会很费解,因为如果发生 IO异常,执行 catch 代码块,执行到 System.exit(2) 时,虚拟机退出, finally 就无法执行了。
这可怎么办呢?
虽然可以使用 字节码工具如 ASM 或者 Javassist 来修改字节码在 System.exit(2) 之前添加跳转到 finally 模块,但是并不符合题意。
难道是要阻止 System.exit(2) 的执行??
2.2 源码分析
java.lang.System#exit
public static void exit(int status) { Runtime.getRuntime().exit(status); }
看底层的源码 java.lang.Runtime#exit
/** * 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. * * // 省略部分注释 * <p> The <tt>{@link System#exit(int) System.exit}</tt> method is the * conventional and convenient means of invoking this method. <p> * * @param status * Termination status. By convention, a nonzero status code * indicates abnormal termination. * * @throws SecurityException * If a security manager is present and its <tt>{@link * SecurityManager#checkExit checkExit}</tt> 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); }
注意:很多人比较急躁,看源码不喜欢看源码注释,提醒大家一定要重视注释。
通过该注释我们知道 System#exit(int) 是为了简化调用,是一种更传统和方便的调用方式。
另外 status 非0 表示非正常终止。
另外 如果一个 安全管理器阻止了某个特定 status 的退出,则会抛出 SecurityException。
因此思路来了,我们如果阻止 status ==2 的退出不就好了?
那么如何阻止呢?
/** * Gets the system security interface. * * @return if a security manager has already been established for the * current application, then that security manager is returned; * otherwise, <code>null</code> is returned. * @see #setSecurityManager */ public static SecurityManager getSecurityManager() { return security; }
查看成员变量
/* The security manager for the system. */ private static volatile SecurityManager security = null;
找到设置的地方:
/** * Sets the System security. * * <p> If there is a security manager already installed, this method first * calls the security manager's <code>checkPermission</code> method * with a <code>RuntimePermission("setSecurityManager")</code> * permission to ensure it's ok to replace the existing * security manager. * This may result in throwing a <code>SecurityException</code>. * * <p> Otherwise, the argument is established as the current * security manager. If the argument is <code>null</code> and no * security manager has been established, then no action is taken and * the method simply returns. * * @param s the security manager. * @exception SecurityException if the security manager has already * been set and its <code>checkPermission</code> method * doesn't allow it to be replaced. * @see #getSecurityManager * @see SecurityManager#checkPermission * @see java.lang.RuntimePermission */ public static void setSecurityManager(final SecurityManager s) { try { s.checkPackageAccess("java.lang"); } catch (Exception e) { // no-op } setSecurityManager0(s); }
因此我们设置一下安全管理器。这里判断 status 为 2 时 ,抛出SecurityExceptio异常。
public static void main(String[] args) { // 修改 SecurityManager System.setSecurityManager(new SecurityManager() { @Override public void checkExit(int status) { if(status ==2){ throw new SecurityException("不允许退出"); } } }); try { BufferedReader br = new BufferedReader(new FileReader("file.txt")); System.out.println(br.readLine()); br.close(); } catch (Exception e) { System.exit(2); } finally { System.out.println("Exiting the program"); } }
因此内部抛出 IO异常之后, catch 代码块执行到 System.exit(2); 内部执行到 security.checkExit(status); 时抛出 SecurityException。
public void exit(int status) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkExit(status); } Shutdown.exit(status); }
这样 fianlly 代码块就可以得到执行。
执行效果:
Exiting the program
Exception in thread "main" java.lang.SecurityException: 不允许退出
at com.imooc.basic.learn_exit.ExitTest$1.checkExit(ExitTest.java:12)
at java.lang.Runtime.exit(Runtime.java:107)
at java.lang.System.exit(System.java:971)
at com.imooc.basic.learn_exit.ExitTest.main(ExitTest.java:20)
另外最初catch那里是是 IOException weibo_LittleYul 练习是遇到了问题,进行了探讨,给出了不错的答案。
思考题我跟着一些写,但是结果不一样,会发生一下异常 Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "log4j.xml" "read") 我跟着源码进去看了,奈何没太看懂,SecurityManager#hasAllPermission返回的是false,可以理解自己创建的SecurityManager没有权限吗~
然后提示他此时已经实现了效果,如果不想出现这个异常该怎么做呢?
该同学给出了几种不错的方案:
1 直接修改安全管理器,重写权限检查的功能
2 修改policy文件,提供文件读的权限
需要通过-Djava.security.policy指定policy文件,在里面加上对应文件的io权限就可以了。
之前是我没整明白grant语句的语法,误以为给了全部权限,实际上该grant语句只对file:${{java.ext.dirs}}/*下的代码给力全部权限。
所以我们自己的代码还是没有权限去读取文件,需要自己授权。
2.3 意义
可能有些同学会觉得这种题目“没意义”。其实潜台词就是面试的时候不太可能会问到,其次平时开发的时候用的不多。
不知道有没有同学发现,高考对我们带来的影响一直从未消逝。很多人学习技术更多地是为了功利性地考试或者求职,其他的一概是“无用”。
这就导致技术的深度和广度不够,因为稍微深点的东西就不愿意学,觉得没必要;稍微广点的东西,当前不用就觉得不着急学。
然而,不同的面试官的喜好不同,有些面试官可能会问到这些问题。
如果你回答都是别人也会的“标准答案”,那你的优势在哪里?
如果所有技术问题都不深究,不管是校招还是社招又如何能超出面试官的期待呢?
举一个非常简单的例子
如果面试官问你 Integer a = 200,b =200; 问 a==b 的结果。
有一部分同学可能搞不清楚;
有一部分同学看过《阿里巴巴Java开发手册》或者一些面经,窃喜,颇有自信地回答出“标准答案”为 false 因为缓存了 -128 到 127 的整数对象,不在缓存区间所以为false。
真的如此吗?
其实最大值是可以设置的,因此答案也可能是true。
有很大一部分同学由于没有实际去看源码,没有看到有资料提到这一点,而不知道。
很少有资料告诉你为什么要缓存这一区间,缓存的本质是什么?
缓存这一段整数对象是 Java语言规范的要求,某种程度上是因为这一段数据更常用,因此提前构造好。
缓存主要体现了空间换时间的思想。
就像这个问题,面试的时候很多人自己觉得回答的不错,而被淘汰。从回答问题本身而言,深度还不够,还不全面,没有超出面试官的期待,甚至还没回答到问题的关键。
3 总结
本文解答 《阿里巴巴Java开发手册》详解专栏的 16节 JVM的退出时机的课后题。
并探讨了研究这个题目的意义,目的之一是帮助大家养成看源码的习惯,培养解决问题的能力,目的之二是希望大家能够有一定的技术追求,主动提升技术的深度和广度。
希望大家在看其他资料或者学专栏是,更加重视分析和解决问题的步骤,而不是记忆答案。
因为分析和解决问题是一种能力,有助于帮助你解决其他问题,而记忆的信息容易遗忘。
---------------------------------
如果本文对你有帮助,欢迎点赞、评论和转发,你的支持是我创作的最大动力。
另外想学习,更多开发和避坑技巧,少走弯路,请关注《阿里巴巴Java 开发手册》详解专栏
共同学习,写下你的评论
评论加载中...
作者其他优质文章