Java: System.exit() 与安全策略

简介: 说明System.exit() 的本质是通知 JVM 关闭。一般来说,有两种禁用 System.exit() 的办法:安全管理器安全策略本质都是JRE 提供的本地实现,在执行之前进行权限判断。

说明

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.Shutdownjava.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 {
    @Override
    public void checkPermission(Permission perm) {
        // allow anything.
    }

    @Override
    public void checkPermission(Permission perm, Object context) {
        // allow anything.
    }

    @Override
    public 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 框架捕获, 然后就会报告说某某某测试执行失败,然后就继续下一个单元测试了。

当然,给出的解决方案就是前面的那段代码. 你还可以阅读下面的参考文章,查找其他的解决方案。

参考文章:

日期: 2015年08月25日

人员: 铁锚 http://blog.csdn.net/renfufei

目录
相关文章
|
3月前
|
缓存 Java
Java本地高性能缓存实践问题之Caffeine缓存库中基于时间设置驱逐策略的问题如何解决
Java本地高性能缓存实践问题之Caffeine缓存库中基于时间设置驱逐策略的问题如何解决
|
13天前
|
消息中间件 监控 算法
Java性能优化:策略与实践
【10月更文挑战第21】Java性能优化:策略与实践
|
23天前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
36 1
|
29天前
|
设计模式 监控 算法
Java设计模式梳理:行为型模式(策略,观察者等)
本文详细介绍了Java设计模式中的行为型模式,包括策略模式、观察者模式、责任链模式、模板方法模式和状态模式。通过具体示例代码,深入浅出地讲解了每种模式的应用场景与实现方式。例如,策略模式通过定义一系列算法让客户端在运行时选择所需算法;观察者模式则让多个观察者对象同时监听某一个主题对象,实现松耦合的消息传递机制。此外,还探讨了这些模式与实际开发中的联系,帮助读者更好地理解和应用设计模式,提升代码质量。
Java设计模式梳理:行为型模式(策略,观察者等)
|
1月前
|
安全 Java 编译器
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型通过参数化类型实现了代码重用和类型安全,提升了代码的可读性和灵活性。本文深入探讨了泛型的基本原理、常见用法及局限性,包括泛型类、方法和接口的使用,以及上界和下界通配符等高级特性。通过理解和运用这些技巧,开发者可以编写更健壮和通用的代码。
|
2月前
|
安全 Java API
java安全特性
java安全特性
28 8
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
62 11
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
92 11
|
3月前
|
安全 Java 程序员
Java编程中实现线程安全的策略
【8月更文挑战第31天】在多线程环境下,保证数据一致性和程序的正确运行是每个程序员的挑战。本文将通过浅显易懂的语言和实际代码示例,带你了解并掌握在Java编程中确保线程安全的几种策略。让我们一起探索如何用同步机制、锁和原子变量等工具来保护我们的数据,就像保护自己的眼睛一样重要。
|
3月前
|
IDE Java 测试技术
揭秘Java高效编程:测试与调试实战策略,让你代码质量飞跃,职场竞争力飙升!
【8月更文挑战第30天】在软件开发中,测试与调试对确保代码质量至关重要。本文通过对比单元测试、集成测试、调试技巧及静态代码分析,探讨了多种实用的Java测试与调试策略。JUnit和Mockito分别用于单元测试与集成测试,有助于提前发现错误并提高代码可维护性;Eclipse和IntelliJ IDEA内置调试器则能快速定位问题;Checkstyle和PMD等工具则通过静态代码分析发现潜在问题。综合运用这些策略,可显著提升代码质量,为项目成功打下坚实基础。
58 2