JVM- 技术专题 - 关闭与关闭钩子
1. 简述JVM关闭
通常而言,对于JVM的关闭我们很少去关注,但是了解JVM的关闭能帮我们在JVM关闭时做一些合理的事情。首先JVM的关闭方式可以分为三种:
正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等)
强制关闭:通过调用Runtime.halt()方法或在操作系统中直接kill(发送SIGKILL信号)掉JVM进程
异常关闭:运行中遇到RuntimeException异常等。
某些情况下,我们需要在JVM关闭时做些扫尾的工作,比如删除临时文件、停止日志服务以及内存数据写到磁盘等,为此JVM提供了关闭钩子(shutdown hooks)来做这些事情。另外特别注意的是:如果JVM因异常关闭,那么子线程(Hook本质上也是子线程)将不会停止。
但在JVM被强行关闭时,这些线程都会被强行结束。
了解关闭钩子之前我们首先认识Runtime。Runtime封装Java应用运行时的环境。通过Runtime实例,使得应用程序和其运行环境相连接。Runtime是在应用启动期间自动建立,应用程序不能够创建Runtime,但是我们可以通过Runtime.getRuntime()
来获得当前应用的Runtime对象引用,通过该引用我们可以获得当前运行环境的相关信息,比如空闲内存、最大内存以及为当前虚拟机添加关闭钩子(addShutdownHook()
),执行指定命令(exec()
)等等。
2. 深入关闭钩子(shutdown hook)
关闭钩子本质上是一个线程(也称为Hook线程),用来监听JVM的关闭。通过使用Runtime的addShutdownHook(Thread hook)可以向JVM注册一个关闭钩子。Hook线程在JVM 正常关闭才会执行,在强制关闭时不会执行。
对于JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不能保证它的执行顺行。当所有的Hook线程执行完毕后,如果此时runFinalizersOnExit为true,那么JVM将先运行终结器,然后停止。
Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间。另外由于多个钩子是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议在一个钩子中执行一系列操作。
另外在使用关闭钩子还要注意以下几点:
1. 不能在钩子调用
System.exit()
,否则卡住JVM的关闭过程,但是可以调用Runtime.halt()
。2. 不能再钩子中再进行钩子的添加和删掉操作,否则将会抛出IllegalStateException。
3. 在
System.exit()
之后添加的钩子无效。4. 当JVM收到SIGTERM命令(比如操作系统在关闭时)后,如果钩子线程在一定时间没有完成,那么Hook线程可能在执行过程中被终止。
5. Hook线程中同样会抛出异常,如果抛出异常又不处理,那么钩子的执行序列就会被停止。
源码解析
现在我们来通过源码来简单的分析一下shutdown hook的实现原理。简单的说,Shutdown类充当触发器,负责触发关闭钩子,而ApplicationShutdownHooks负责管理所有注册到JVM的关闭钩子。
1. Shutdown
2. ApplicationShutdownHooks
接着再来看一下ApplicationShutdownHooks类,该类用来管理和维护用户等级的钩子,也就是Shutdown提到的Application hooks。该类中通过使用一个Map性质的IdentityHashMap来保持用户的关闭钩子。
这里需要注意几点:
在初始化阶段,当static{}执行时,
runHooks()
并未执行。在
runHooks()
中通过定义局部的threads来转移hooks,转移成功后,hooks将被清空,这样就保证了,关闭钩子的逻辑只能执行一次。例如你在代码中多处调用System.exit()方法,但关闭钩子的执行逻辑只能运行一次。随后启动所有用户Hook线程,再通过调用每个join()的来使主线程等待子线程执行完毕,这也就保证了在正常情况下关闭钩子会在应用最终关闭前完成。任何一个应用都会默认存在一个Appliction Hook,该Hook被Shutdown触发后将会引起所有已注册到JVM的ShutdownHook的执行。
3. 综合应用:
明白了其原理之后,也需要知道其使用场景:
1、内存管理
在某些情况下,我们需要根据当前内存的使用情况,人为的调用System.gc()
来尝试回收堆内存中失效的对象。此时就可以用到Runtime中的totalMemory()
、freeMemory()
等方法。示例如下:
2、执行命令
注意:通过exec()
方式执行命令时,该命令在单独的进程(Process)中。
3、通过Hook实现临时文件清理
实例:
评论