07_05_程序关闭
程序关闭
JVM 既可以正常关闭,也可以强行关闭。
正常关闭方式有:
- 最后一个“正常(非守护)”线程结束时
- 调用了
System.exit()
方法 - 通过其他特定于平台的方法(比如发送 SIGNAL 信号,或键入 CRTL + C)
强制关闭方式有:
- 调用了
Runtime.getRuntime().halt()
方法 - 在操作系统中“杀死” JVM 进程
不同的关闭方式,JVM 可能会有不同的表现。
一、关闭钩子
关闭钩子是指通过 Runtime.addShutdownHook(Thread hook)
注册的但未开始的线程。
注意,关闭钩子是一个 Thread 线程对象,执行时是按照线程的方式运行。
正常关闭 JVM 时,会触发关闭钩子的执行:
- 首先调用所有已注册的关闭钩子,关闭钩子(也就是线程)是并发执行的
- 接着当所有的关闭钩子执行结束后,如果
runFinalizersOnExit
属性为 true,则调用所有已注册的finalizer
钩子 - 最后 JVM 进入终结阶段,JVM 会强制关闭,剩余的还未结束的线程(守护或非守护线程)也会被强制结束
强制关闭 JVM 时,不会运行关闭钩子。
关闭钩子的设计应该满足:
- 关闭钩子应该是线程安全的,访问共享数据必须使用同步机制
- 关闭钩子不应该对应用程序的状态或 JVM 的关闭原因做出任何假设,不要依赖程序的状态
- 关闭钩子之间不应该有依赖关系,否则可能会导致死锁
- 关闭钩子必须尽快退出,避免延迟 JVM 关闭时间
- 关闭钩子不应该抛出异常,避免导致 JVM 关闭失败
关闭钩子的设计应该要尽量简单,比如做一些程序的清理工作。
二、守护线程
- 线程可以分为2种:普通线程和守护线程
- 守护线程和普通线程之间的差异仅在于当线程退出时发生的操作
- 守护线程是一个特殊的线程,它不会阻碍程序关闭,而是会在程序关闭时自动结束
- 尽可能少用守护线程,因为它很容易就被抛弃,而大部分情况下线程都需要在退出时清理资源
- 守护线程最好用于执行“内部”任务,执行一些周期性任务,比如清理缓存等
三、终结器
- 垃圾回收器在回收垃圾对象时,会先执行对象中定义的终结器
finalize()
- 避免使用终结器,终结器并不保证它会在何时运行,是否会运行
总结
程序的关闭,需要各个环节的相互配合才能完成:
- 程序关闭,需要等待服务停止
- 服务停止,需要等待线程结束
- 线程结束,需要等待任务完成/取消
- 任务取消,需要通过中断来完成