Java 优雅退出

前言

先思考几个问题

  1. Java 服务为什么会挂掉?
  2. 什么情况 Java 进程会挂掉?
  3. Java 进程挂掉如何做优雅退出?
  4. OOM 导致挂掉为何还能实现优雅退出?
  5. OOM 一定会导致进程挂掉吗?
  6. 你还在用 kill -9 杀进程?为什么不建议这样做?

Java 进程为什么会挂,导致挂的原因有哪些?

  • linux 的 OOM killer 杀死 : Linux 内核有个机制叫 OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉 。可以查看内核日志文件 /var/log/message 去定位此类问题。
  • JVM 自身故障: 当 JVM 发生致命错误导致崩溃时,会生成一个 hs_err_pid_xxx.log 这样的文件
  • JVM 的 OOM 导致进程退出 : -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=*/java.hprof

Java 优雅退出

JDK 提供了 Java.Runtime.addShutdownHook(Thread hook) 方法,可以注册一个JVM 关闭的钩子,这个钩子可以在一下几种场景中被调用:

  1. 程序正常退出
  2. 使用 System.exit()
  3. 终端使用 Ctrl+C 触发的中断
  4. 系统关闭
  5. OutOfMemory 宕机
  6. 使用 Kill pid 命令干掉进程(注:在使用 kill -9 pid 时,是不会被调用的)

outofmemory 的时候为什么还能调用勾子实现优雅退出?

OutOfMemoryError: 是一种普通的错误

Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. OutOfMemoryError objects may be constructed by the virtual machine as if suppression were disabled and/or the stack trace was not writable.

  • Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。
  • Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

模拟 Java OOM 宕机测试,参考:https://www.jianshu.com/p/f7d9cd70e2c6
JVM 配置:-Xmx1M -XX:HeapDumpPath=d:/oome_test_dump.hprof -XX:+HeapDumpOnOutOfMemoryError

        System.out.println("main process start");
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("graceful exit by hook...");
            }
        }));

        List list = new ArrayList();
        // 通过死循环迫使内存溢出
        while(true) {
            list.add(new Long(0));
        }
    }

OOME 后 JVM 先 dump heap 文件,然后执行优雅退出 hook,进程最后退出

main process start
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to d:/oome_test_dump.hprof ...
graceful exit by hook...
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at javabase.OOMTest.main(OOMTest.java:22)

Process finished with exit code 1

发生 OOM 会导致 Java 进程挂掉吗?

OOME 不一定会导致 java 进程退出,例如启动两个线程,一个循环打印,一个让其内存溢出,发生 OOME 后,进程没有退出。

static void test2() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread 1 start....");
                while (true) {
                    System.out.println("thread 1 is alive...");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread 2 start....");

                List list = new ArrayList();
                while (true) {
                    list.add(new Long(0));
                }
            }
        }).start();
    }

打印如下:
Java 优雅退出_第1张图片
image.png

线程 2 发生 OOME 之后 线程 1 还在正常工作,此时查看内存,线程 2 申请的内存应该会被回收了。

如果需要 OOME 后,进程退出,需要配置(java 8u92 版本+):
-XX:+ExitOnOutOfMemoryError
-XX:+CrashOnOutOfMemoryError

ExitOnOutOfMemoryError
When you enable this option, the JVM exits on the first occurrence of an out-of-memory error. It can be used if you prefer restarting an instance of the JVM rather than handling out of memory errors.
CrashOnOutOfMemoryError
If this option is enabled, when an out-of-memory error occurs, the JVM crashes and produces text and binary crash files.

参考:https://stackoverflow.com/questions/12096403/java-shutting-down-on-out-of-memory-error

你可能感兴趣的:(Java 优雅退出)