从JDK源码看System.exit

前言

在编写的Java程序中有时会遇到用 System.exit 来关闭JVM,其中调用 exit 方法时会包含一个状态参数n,即System.exit(n)。这其实是一个约定值,如果为0则表示正常关闭,而非0则表示非正常关闭。这里我们从JDK源码看下不同状态都是怎么处理的。

System与Runtime

先看System类的exit方法如下,可以看到它是间接调用了Runtime对象的exit方法。

public static void exit(int status) {
    Runtime.getRuntime().exit(status);
}

而Runtime的exit方法如下,先使用SecurityManager检查是否有关闭JVM的权限,允许执行则调用Shutdown的exit方法。

public void exit(int status) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkExit(status);
    }
    Shutdown.exit(status);
}

Shutdown

进入到Shutdown类的exit方法,Java层面还有自己的状态state,它可能值为RUNNING、HOOKS和FINALIZERS,可以看到里面的主要逻辑是:
1. 不管什么状态下,status为非0时不执行任何Finalizer。
2. 在RUNNING状态下,状态转成HOOKS,然后先执行sequence方法,再执行halt方法停止JVM。
3. 在FINALIZERS状态下,status为非0时直接 就调用halt方法停止JVM了,而status为0时还需要执行所有的finalizer,之后才调用halt方法停止JVM。

    static void exit(int status) {
        boolean runMoreFinalizers = false;
        synchronized (lock) {
            if (status != 0) runFinalizersOnExit = false;
            switch (state) {
            case RUNNING:
                state = HOOKS;
                break;
            case HOOKS:
                break;
            case FINALIZERS:
                if (status != 0) {
                    halt(status);
                } else {
                    runMoreFinalizers = runFinalizersOnExit;
                }
                break;
            }
        }
        if (runMoreFinalizers) {
            runAllFinalizers();
            halt(status);
        }
        synchronized (Shutdown.class) {
            sequence();
            halt(status);
        }
    }

sequence方法主要是控制钩子和Finalizer执行的顺序,判断状态不为HOOKS则直接返回,然后执行所有的钩子,把state改为FINALIZERS,最后执行所有finalizer。

private static void sequence() {
        synchronized (lock) {
            if (state != HOOKS) return;
        }
        runHooks();
        boolean rfoe;
        synchronized (lock) {
            state = FINALIZERS;
            rfoe = runFinalizersOnExit;
        }
        if (rfoe) runAllFinalizers();
    }

halt方法

执行JVM是通过halt方法实现,这时System.exit(n)的状态n继续往下传递,最终是调用了一个本地的halt0方法。

static void halt(int status) {
    synchronized (haltLock) {
        halt0(status);
    }
}

static native void halt0(int status);

对应的本地方法如下,主要是调用了JVM_Halt函数,

JNIEXPORT void JNICALL
Java_java_lang_Shutdown_halt0(JNIEnv *env, jclass ignored, jint code)
{
    JVM_Halt(code);
}

继续往下,JVM_Halt函数主要包含了before_exit函数和vm_exit函数,before_exit函数主要做退出前的一些工作,它只会被执行一次,在多个线程情况下只有获取锁的才能执行,其他线程都必须等。

JVM_ENTRY_NO_ENV(void, JVM_Halt(jint code))
  before_exit(thread);
  vm_exit(code);
JVM_END

而vm_exit函数如下,这里code仍然是Java调用System.exit(n)时传递来的,最主要的是vm_direct_exit函数,它先向jvm发出关闭通知,然后再调用exit函数退出,状态值继续往下传,这时的状态值已经传递到操作系统的API。

void vm_exit(int code) {
  Thread* thread = ThreadLocalStorage::is_initialized() ?
    ThreadLocalStorage::get_thread_slow() : NULL;
  if (thread == NULL) {
    vm_direct_exit(code);
  }

  if (VMThread::vm_thread() != NULL) {
    VM_Exit op(code);
    if (thread->is_Java_thread())
      ((JavaThread*)thread)->set_thread_state(_thread_in_vm);
    VMThread::execute(&op); VM Thread.
    vm_direct_exit(code);
  } else {
    vm_direct_exit(code);
  }
  ShouldNotReachHere();
}
void vm_direct_exit(int code) {
  notify_vm_shutdown();
  os::wait_for_keypress_at_exit();
  ::exit(code);
}

总结

Java的System.exit(n)的状态码最终是传递到操作系统的API,所以它的含义与操作系统API的含义相关,当然这个过程Java还会有自己的一些机制工作需要处理。可以说目前大多数平台都可以在 main 函数中直接 return退出程序,但某些平台下不能这样处理,所以为了兼容需要使用 exit() 来退出。

以下是广告相关阅读

========广告时间========

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

=========================

相关阅读:

从JDK源码角度看Object
从JDK源码角度看Long
从JDK源码角度看Float
从JDK源码角度看Integer
volatile足以保证数据同步吗
谈谈Java基础数据类型
从JDK源码角度看并发锁的优化
从JDK源码角度看线程的阻塞和唤醒
从JDK源码角度看并发竞争的超时
从JDK源码角度看java并发线程的中断
从JDK源码角度看Java并发的公平性
从JDK源码角度看java并发的原子性如何保证
从JDK源码角度看Byte
从JDK源码角度看Boolean
从JDK源码角度看Short

关注打赏:

时间: 2024-08-04 04:38:20

从JDK源码看System.exit的相关文章

从JDK源码看Writer

概况 Writer 是一个用于写字符流的抽象类,它将一些相通的写相关操作抽象到此类,方便各种写操作类的实现.一般来说子类只需要实现它的 write.flush .close 等三个方法,但如果有需要还可以重写 Writer 提供的公共方法. JDK 在 Writer 的基础上实现了很多有用的 xxxWriter ,包括 BufferedWriter.CharArrayWriter.FilterWriter.OutputStreamWriter.FileWriter.PipedWriter.Str

从JDK源码看关闭钩子

关闭钩子 Java提供了Shutdown Hook机制,它让我们在程序正常退出或者发生异常时能有机会做一些清场工作.使用的方法也很简单,Java.Runtime.addShutdownHook(Thread hook)即可.关闭钩子其实可以看成是一个已经初始化了的但还没启动的线程,当JVM关闭时会并发地执行注册的所有关闭钩子. 钩子执行时机 向JVM注册关闭钩子后的什么时候会被调用,什么时候不会被调用呢?分成以下情况: Java程序正常运行完退出时会被调用. windows和linux终端中通过

从JDK源码看InputStream

概况 JDK 给我们提供了很多实用的输入流 xxxInputStream,而 InputStream 是所有字节输入流的抽象.包括 ByteArrayInputStream .FilterInputStream .BufferedInputStream .DataInputStream 和 PushbackInputStream 等等. 继承结构 --java.lang.Object --java.io.InputStream 类定义 public abstract class InputStr

从JDK源码角度看Float

关于IEEE 754 在看Float前需要先了解IEEE 754标准,该标准定义了浮点数的格式还有一些特殊值,它规定了计算机中二进制与十进制浮点数转换的格式及方法.规定了四种表示浮点数值的方法,单精确度(32位).双精确度(64位).延伸单精确度(43位以上)与延伸双精确度(79位以上).多数编程语言支持单精确度和双精确度,这里讨论的Float就是Java的单精确度的实现. 浮点数的表示 浮点数由三部分组成,如下图,符号位s.指数e和尾数f. 对于求值我们是有一个公式对应的,根据该公式来看会更简

从JDK源码角度看Byte

Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型之间的转换方法. 主要实现代码如下: public final class Byte extends Number implements Comparable<Byte> { public static final byte MIN_VALUE = -128; public static fina

从JDK源码角度看Long

概况 Java的Long类主要的作用就是对基本类型long进行封装,提供了一些处理long类型的方法,比如long到String类型的转换方法或String类型到long类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Long 主要属性 public static final long MIN_VALUE = 0x8000000000000000L;

从JDK源码角度看Integer

概况 Java的Integer类主要的作用就是对基本类型int进行封装,提供了一些处理int类型的方法,比如int到String类型的转换方法或String类型到int类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Integer 主要属性 第一部分 public static final int MIN_VALUE = 0x80000000; pub

从JDK源码角度看Short

概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然也包含与其他类型之间的转换方法. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Short 主要属性 public static final short MIN_VALUE = -32768; public static final s

从JDK源码角度看Boolean

Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public final class Boolean implements java.io.Serializable, Comparable<Boolean> { private final boolean value; public static final Boolean TRUE = new Boolean(