JVM Shutdown Hook
共 6806字,需浏览 14分钟
·
2023-06-14 13:34
须弥零一JVM Shutdown Hook
今天偶然看到 java.lang.Runtime
类的一个方法 public void addShutdownHook(Thread hook)
。
它的 javadoc
是这么写的:
Registers a new virtual-machine shutdown hook.
The Java virtual machine shuts down in response to two kinds of events:
• The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or
• The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.
意思就是说可以给 JVM
注册一个钩子,这个钩子将在虚拟机关闭的执行。当然这个 关闭 是有条件的。
写个例子
public
class
ShutdownHookTest
{
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
"Main thread start"
);
Thread
hook
=
new
Thread
(
new
MyShutdownHook
());
Runtime
.
getRuntime
().
addShutdownHook
(
hook
);
System
.
out
.
println
(
"Main thread end"
);
}
static
class
MyShutdownHook
implements
Runnable
{
@Override
public
void
run
()
{
System
.
out
.
println
(
"-- my shutdown hook start --"
);
try
{
System
.
out
.
println
(
"-- do hook task --"
);
Thread
.
sleep
(
5
*
1000L
);
System
.
out
.
println
(
"-- hook task finished --"
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
System
.
out
.
println
(
"-- my shutdown hook end --"
);
}
}
}
使用 javac
编译后运行,得到的输出结果是:
Main
thread start
Main
thread
end
--
my
shutdown hook start
--
--
do
hook task
--
--
hook task finished
--
--
my
shutdown hook
end
--
结果符合文章刚开始引文的第一个情况,当程序的正常退出时会执行注册的钩子。也就是说,在程序主线程(实际上是所有的 demon线程
)结束后,会启动执行钩子线程。
程序非执行完成推出的例子
稍微改一下上面的代码:
public
class
ShutdownHookTest
{
public
static
void
main
(
String
[]
args
)
throws
Exception
{
System
.
out
.
println
(
"Main thread start"
);
Thread
hook
=
new
Thread
(
new
MyShutdownHook
());
Runtime
.
getRuntime
().
addShutdownHook
(
hook
);
// --- 改了这里 ---
Thread
.
sleep
(
120
*
1000L
);
// --------------
System
.
out
.
println
(
"Main thread end"
);
}
static
class
MyShutdownHook
implements
Runnable
{
@Override
public
void
run
()
{
System
.
out
.
println
(
"-- my shutdown hook start --"
);
try
{
System
.
out
.
println
(
"-- do hook task --"
);
Thread
.
sleep
(
5
*
1000L
);
System
.
out
.
println
(
"-- hook task finished --"
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
System
.
out
.
println
(
"-- my shutdown hook end --"
);
}
}
}
同样使用 javac
编译后运行。不同的是,在程序启动后按 ctrl + c
停止程序,将得到下面的输出:
Main
thread start
--
my
shutdown hook start
--
--
do
hook task
--
--
hook task finished
--
--
my
shutdown hook
end
--
这个结果也符合文章开始引文的第二个情况。当虚拟机用为用户输入 ^C
时,虚拟机会调用已注册的钩子。
另外因为也提到了当用户注销和系统关闭时也会调用已注册的钩子,这里就不做验证了。
钩子不能执行的情况
同样是上面的例子。程序在启动后,打开任务管理器(Windows),找到对应的进程并结束。这时控制台的输出为:
Main
thread start
从输出可以看到,钩子并没有执行。
这就说明,在虚拟机中止 (注意:这里的中止不同于退出或停止,是指异常的break) 的情况下,钩子不会被调用执行。
javadoc
也给出了这种情况的说明:
在极少数虚拟机被外部中止的情况下,例如:
• 在 Unix 上使用 SIGKILL 信号
• 在 Windows 上使用 TerminateProcess 调用
• 执行本地方法出错
也就是说,虚拟机在没有干净地关闭的情况下停止运行,虚拟机则不能保证是否正确的运行关机钩子。
移除钩子
移除钩子使用方法 public boolean removeShutdownHook(Thread hook)
即可。
使用场景
看到这个特性,第一个想到的场景就是。程序在关闭时可以记录一个日志或发送一个通知。
当然,这个基于这个特性,可以定制出来很多使用场景。
但是,鉴于这个钩子的执行时机,就有很多需要注意的地方:
-
• 关机钩子(shutdown hook)必须是一个已初始化但未启动的线程
-
• 如果注册了多个钩子,则不能保证这些钩子的执行顺序,他们是同时开始的
-
• 当虚拟机关机序列开始,则无法在注册或取消钩子
-
• 对于钩子的线程的编写,应该是线程安全的,并尽可能地避免出现死锁
-
• 钩子的执行时间不应过长,也不应该添加任何用户交互功能
-
• 这时因为当用户注销或者关机时,底层操作系统可能只允许有限的固定时间来关闭和退出虚拟机
-
最后
文章就写到这里。
这篇文章没啥深入的探究,只是突然发现了一个之前未曾注意到的功能,简单的做一下记录和测试。
大家如果要在生产环境中使用,要是场景复杂还是慎重些,上一章节的那些注意事项都需要考虑考虑。
---- END ----欢迎关注我的公众号“须弥零一”,原创技术文章第一时间推送。