07_05_程序关闭

程序关闭

JVM 既可以正常关闭,也可以强行关闭。

正常关闭方式有:

  • 最后一个“正常(非守护)”线程结束时
  • 调用了 System.exit() 方法
  • 通过其他特定于平台的方法(比如发送 SIGNAL 信号,或键入 CRTL + C)

强制关闭方式有:

  • 调用了 Runtime.getRuntime().halt() 方法
  • 在操作系统中“杀死” JVM 进程

不同的关闭方式,JVM 可能会有不同的表现。

一、关闭钩子

关闭钩子是指通过 Runtime.addShutdownHook(Thread hook) 注册的但未开始的线程。

注意,关闭钩子是一个 Thread 线程对象,执行时是按照线程的方式运行。

正常关闭 JVM 时,会触发关闭钩子的执行:

  1. 首先调用所有已注册的关闭钩子,关闭钩子(也就是线程)是并发执行的
  2. 接着当所有的关闭钩子执行结束后,如果 runFinalizersOnExit 属性为 true,则调用所有已注册的 finalizer 钩子
  3. 最后 JVM 进入终结阶段,JVM 会强制关闭,剩余的还未结束的线程(守护或非守护线程)也会被强制结束

强制关闭 JVM 时,不会运行关闭钩子。

关闭钩子的设计应该满足:

  • 关闭钩子应该是线程安全的,访问共享数据必须使用同步机制
  • 关闭钩子不应该对应用程序的状态或 JVM 的关闭原因做出任何假设,不要依赖程序的状态
  • 关闭钩子之间不应该有依赖关系,否则可能会导致死锁
  • 关闭钩子必须尽快退出,避免延迟 JVM 关闭时间
  • 关闭钩子不应该抛出异常,避免导致 JVM 关闭失败

关闭钩子的设计应该要尽量简单,比如做一些程序的清理工作。

二、守护线程

  • 线程可以分为2种:普通线程和守护线程
  • 守护线程和普通线程之间的差异仅在于当线程退出时发生的操作
  • 守护线程是一个特殊的线程,它不会阻碍程序关闭,而是会在程序关闭时自动结束
  • 尽可能少用守护线程,因为它很容易就被抛弃,而大部分情况下线程都需要在退出时清理资源
  • 守护线程最好用于执行“内部”任务,执行一些周期性任务,比如清理缓存等

三、终结器

  • 垃圾回收器在回收垃圾对象时,会先执行对象中定义的终结器 finalize()
  • 避免使用终结器,终结器并不保证它会在何时运行,是否会运行

总结

程序的关闭,需要各个环节的相互配合才能完成:

  • 程序关闭,需要等待服务停止
  • 服务停止,需要等待线程结束
  • 线程结束,需要等待任务完成/取消
  • 任务取消,需要通过中断来完成
作者

jiaduo

发布于

2022-05-15

更新于

2023-04-03

许可协议