Java: System.exit() 与安全策略
說明
System.exit() 的本質是通知 JVM 關閉。
一般來說,有兩種禁用 System.exit() 的辦法:
- 安全管理器
- 安全策略
本質都是JRE 提供的本地實現,在執行之前進行權限判斷。
因為System.exit() 是一種很暴力的手段,如果在 Client 模式下自己寫個小程序無所謂,但是在 Server 上多個程序、或者多線程時就會有很大的麻煩。
底層源碼
1.先來看看靜態方法 System.exit() 的源碼:
// System.exit() public static void exit(int status) {Runtime.getRuntime().exit(status); }應該說很簡單, 只是簡單地調用運行時的 exit 方法.
2.然后我們看運行時的實例方法 exit:
// Runtime.exit() public void exit(int status) {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkExit(status);}Shutdown.exit(status); }如果有安全管理器,那么就讓安全管理器執行 checkExit退出權限檢查。
如果檢查不通過,安全管理器就會拋出異常(這就是約定!)。
然后當前線程就會往外一路拋異常,如果不捕獲,那么該線程就會退出。
此時如果沒有其他的前臺線程正在運行,那么JVM也會跟著退出。
3.Shutdown 是 java.lang 包下面的一個類。
訪問權限是 default, 所以我們在API中是不能調用的。
// Shutdown.exit() static void exit(int status) {boolean runMoreFinalizers = false;synchronized (lock) {if (status != 0) runFinalizersOnExit = false;switch (state) {case RUNNING: /* Initiate shutdown */state = HOOKS;break;case HOOKS: /* Stall and halt */break;case FINALIZERS:if (status != 0) {/* Halt immediately on nonzero status */halt(status);} else {/* Compatibility with old behavior:* Run more finalizers and then halt*/runMoreFinalizers = runFinalizersOnExit;}break;}}if (runMoreFinalizers) {runAllFinalizers();halt(status);}synchronized (Shutdown.class) {/* Synchronize on the class object, causing any other thread* that attempts to initiate shutdown to stall indefinitely*/sequence();halt(status);} }其中有一些同步方法進行鎖定。 退出邏輯是調用了 halt 方法。
// Shutdown.halt() static void halt(int status) {synchronized (haltLock) {halt0(status);} }static native void halt0(int status);然后就是調用 native 的 halt0() 方法讓 JVM “自殺“了。
示例
使用安全管理器的實現代碼如下所示:
1.定義異常類, 繼承自 SecurityException
ExitException.java
package com.cncounter.security;public class ExitException extends SecurityException {private static final long serialVersionUID = 1L;public final int status;public ExitException(int status) {super("忽略 Exit方法調用!");this.status = status;} }2.定義安全管理器類, 繼承自 SecurityManager
NoExitSecurityManager.java
package com.cncounter.security;import java.security.Permission;public class NoExitSecurityManager extends SecurityManager {@Overridepublic void checkPermission(Permission perm) {// allow anything.}@Overridepublic void checkPermission(Permission perm, Object context) {// allow anything.}@Overridepublic void checkExit(int status) {super.checkExit(status);throw new ExitException(status);} }其中直接拒絕系統退出。
3.增加一個輔助和測試類,實際使用時你也可以自己進行控制。
NoExitHelper.java
package com.cncounter.security;public class NoExitHelper {/*** 設置不允許調用 System.exit(status)* * @throws Exception*/public static void setNoExit() throws Exception {System.setSecurityManager(new NoExitSecurityManager());}public static void main(String[] args) throws Exception {setNoExit();testNoExit();testExit();testNoExit();}public static void testNoExit() throws Exception {System.out.println("Printing works");}public static void testExit() throws Exception {try {System.exit(42);} catch (ExitException e) {//System.out.println("退出的狀態碼為: " + e.status);}} }在其中,使用了一個 main 方法來做簡單的測試。 控制臺輸出結果如下:
Printing works 退出的狀態碼為: 42 Printing works原問題
原來的問題如下:
I’ve got a few methods that should call System.exit() on certain inputs. Unfortunately, testing these cases causes JUnit to terminate! Putting the method calls in a new Thread doesn’t seem to help, since System.exit() terminates the JVM, not just the current thread. Are there any common patterns for dealing with this? For example, can I subsitute a stub for System.exit()?
大意是:
有一些方法需要測試, 但是在某些特定的輸入時就會調用 System.exit()。這就杯具了,這時候 JUnit 測試也跟著退出了! 用一個新線程來調用這種方法也沒什么用, 因為 System.exit() 會停止JVM , 而不是退出當前線程。有什么通用的模式來處理這種情況嗎? 例如,我能替換掉 System.exit() 方法嗎?
建議如下:
Instead of terminating with System.exit(whateverValue), why not throw an unchecked exception? In normal use it will drift all the way out to the JVM’s last-ditch catcher and shut your script down (unless you decide to catch it somewhere along the way, which might be useful someday).
In the JUnit scenario it will be caught by the JUnit framework, which will report that such-and-such test failed and move smoothly along to the next.
翻譯如下:
在程序中調用 System.exit(whateverValue) 是一種很不好的編程習慣, 這種情況為什么不拋出一個未檢測的異常(unchecked exception)呢? 如果程序中不進行捕獲(catch), 拋出的異常會一路漂移到 JVM , 然后就會退出程序(只有主線程的話)。
在 JUnit 的測試場景中異常會被 JUnit 框架捕獲, 然后就會報告說某某某測試執行失敗,然后就繼續下一個單元測試了。
當然,給出的解決方案就是前面的那段代碼. 你還可以閱讀下面的參考文章,查找其他的解決方案。
參考文章:
Java: 如何單元測試那種會調用 System.exit() 的方法?
Java中如何禁止 API 調用 System.exit()
如何配置 Tomcat 的安全管理器
日期: 2015年08月25日
人員: 鐵錨 http://blog.csdn.net/renfufei
總結
以上是生活随笔為你收集整理的Java: System.exit() 与安全策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ioremap 与 mmap【转】
- 下一篇: php+mysqli实现批量执行插入、更