赞
踩
目录
欢迎跳转到本文的原文链接:JAVA虚拟机关闭钩子(Shutdown Hook)_朱小厮的博客-CSDN博客_shutdownhook
Java程序经常也会遇到进程挂掉的情况,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。JAVA中的ShutdownHook提供了比较好的方案。
JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在一下几种场景中被调用:
程序正常退出
使用System.exit()
终端使用Ctrl+C触发的中断
系统关闭
OutOfMemory宕机
使用Kill pid命令干掉进程(注:在使用kill -9 pid时,是不会被调用的)
下面是JDK1.7中关于钩子的定义:
public void addShutdownHook(Thread hook)
参数:
hook - An initialized but unstarted Thread object
抛出:
IllegalArgumentException - If the specified hook has already been registered, or if it can be determined that the hook is already running or has already been run
IllegalStateException - If the virtual machine is already in the process of shutting down
SecurityException - If a security manager is present and it denies RuntimePermission("shutdownHooks")
从以下版本开始:
1.3
另请参见:
removeShutdownHook(java.lang.Thread), halt(int), exit(int)
首先来测试第一种,程序正常退出的情况:
- package com.hook;
-
- import java.util.concurrent.TimeUnit;
-
- public class HookTest
- {
- public void start()
- {
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run()
- {
- System.out.println("Execute Hook.....");
- }
- }));
- }
-
- public static void main(String[] args)
- {
- new HookTest().start();
- System.out.println("The Application is doing something");
-
- try
- {
- TimeUnit.MILLISECONDS.sleep(5000);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- }
运行结果:
The Application is doing something
Execute Hook.....
如上可以看到,当main线程运行结束之后就会调用关闭钩子。
下面再来测试第五种情况(顺序有点乱,表在意这些细节):
- package com.hook;
-
- import java.util.concurrent.TimeUnit;
-
- public class HookTest2
- {
- public void start()
- {
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run()
- {
- System.out.println("Execute Hook.....");
- }
- }));
- }
-
- public static void main(String[] args)
- {
- new HookTest().start();
- System.out.println("The Application is doing something");
- byte[] b = new byte[500*1024*1024];
- try
- {
- TimeUnit.MILLISECONDS.sleep(5000);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
-
- }
运行参数设置为:-Xmx20M 这样可以保证会有OutOfMemoryError的发生。
运行结果:
The Application is doing something
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.hook.HookTest2.main(HookTest2.java:22)
Execute Hook.....
可以看到程序遇到内存溢出错误后调用关闭钩子,与第一种情况中,程序等待5000ms运行结束之后推出调用关闭钩子不同。
接下来再来测试第三种情况:
- package com.hook;
-
- import java.util.concurrent.TimeUnit;
-
- public class HookTest3
- {
- public void start()
- {
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run()
- {
- System.out.println("Execute Hook.....");
- }
- }));
- }
-
- public static void main(String[] args)
- {
- new HookTest3().start();
- Thread thread = new Thread(new Runnable(){
-
- @Override
- public void run()
- {
- while(true)
- {
- System.out.println("thread is running....");
- try
- {
- TimeUnit.MILLISECONDS.sleep(100);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- }
-
- });
- thread.start();
- }
-
- }
在命令行中编译:javac com/hook/HookTest3.java
在命令行中运行:java com.hook.HookTest3 (之后按下Ctrl+C)
运行结果:
可以看到效果如预期。
还有几种情况就不一一列出了,有兴趣的读者可以试一下。
欢迎跳转到本文的原文链接:JAVA虚拟机关闭钩子(Shutdown Hook)_朱小厮的博客-CSDN博客_shutdownhook
主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题,同时最近又看了一下 Dubbo 的源码,想重新写一下 Dubbo 相关的文章。
对于一个 java 应用,如果想在关闭应用时,执行一些释放资源的操作一般是通过注册一个 ShutDownHook ,当关闭应用时,不是调用 kill -9 命令来直接终止应用,而是通过调用 kill -15 命令来触发这个 ShutDownHook 进行停机前的释放资源操作。
对于 Dubbo 来说,需要停机前执行的操作包括两部分:
而何为优雅停机呢?就是在集群环境下,有一个应用停机,并不会出现异常。下面来看一下 Dubbo 是怎么做的。
注册ShutDownHook
Duubo 在 AbstractConfig 的静态构造函数中注册了 JVM 的 ShutDownHook,而 ShutdownHook 主要是调用 ProtocolConfig.destroyAll() ,源码如下:
- static {
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- public void run() {
- if (logger.isInfoEnabled()) {
- logger.info("Run shutdown hook now.");
- }
- ProtocolConfig.destroyAll();
- }
- }, "DubboShutdownHook"));
- }
-
ProtocolConfig.destroyAll()
先看一下 ProtocolConfig.destroyAll() 源码:
-
- public static void destroyAll() {
- if (!destroyed.compareAndSet(false, true)) {
- return;
- }
- AbstractRegistryFactory.destroyAll(); //1.
-
- // Wait for registry notification
- try {
- Thread.sleep(ConfigUtils.getServerShutdownTimeout()); //2.
- } catch (InterruptedException e) {
- logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
- }
-
- ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
- for (String protocolName : loader.getLoadedExtensions()) {
- try {
- Protocol protocol = loader.getLoadedExtension(protocolName);
- if (protocol != null) {
- protocol.destroy(); //3.
- }
- } catch (Throwable t) {
- logger.warn(t.getMessage(), t);
- }
- }
- }
-
ProtocolConfig.destroyAll() 有三个比较重要的操作:
而在第3步会在 Exchange 层 对所有打开的连接进行判断其有没有正在执行的请求,如果有会自旋 Sleep 直到设置的 ServerShutdownTimeout 时间或者已经没有正在执行的请求了才会关闭连接,源码如下:
- public void close(final int timeout) {
- startClose();
- if (timeout > 0) {
- final long max = (long) timeout;
- final long start = System.currentTimeMillis();
- if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
- sendChannelReadOnlyEvent();
- }
- while (HeaderExchangeServer.this.isRunning() //判断是否还有正在处理的请求
- && System.currentTimeMillis() - start < max) { //判断是否超时
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- logger.warn(e.getMessage(), e);
- }
- }
- }
- doClose();
- server.close(timeout); //正在的关闭连接
- }
简单的描述一下问题:就是在应用停机时,瞬间会产生大量的报错,比如拿到的数据库连接已经关闭等问题。 其实一看就知道是在停机时还存在正在处理的请求,而这些请求所需要的资源被 Spring 容器所关闭导致的。原来在SpringBoot 启动时会在 refreshContext 操作也注册一个 ShotdownHook 来关闭Spring容器。
- private void refreshContext(ConfigurableApplicationContext context) {
- this.refresh(context);
- if (this.registerShutdownHook) {
- try {
- context.registerShutdownHook();
- } catch (AccessControlException var3) {
- }
- }
- }
而要解决这个问题就需要取消掉这个 ShutDownHook ,然后再 Dubbo 优雅停机执行后关闭 Spring 容器。具体的修改如下:
- public static void main(String[] args) {
- SpringApplication app = new SpringApplication(XxxApplication.class);
- app.setRegisterShutdownHook(false);
- app.run(args);
- }
- public class SpringShutdownHook {
- private static final Logger logger = LoggerFactory.getLogger(SpringShutdownHook.class);
- @Autowired
- private ConfigurableApplicationContext configurableApplicationContext;
-
- public SpringShutdownHook() {
- }
-
- @PostConstruct
- public void registerShutdownHook() {
- logger.info("[SpringShutdownHook] Register ShutdownHook....");
- Thread shutdownHook = new Thread() {
- public void run() {
- try {
- int timeOut = ConfigUtils.getServerShutdownTimeout();
- logger.info("[SpringShutdownHook] Application need sleep {} seconds to wait Dubbo shutdown", (double)timeOut / 1000.0D);
- Thread.sleep((long)timeOut);
- this.configurableApplicationContext.close();
- logger.info("[SpringShutdownHook] ApplicationContext closed, Application shutdown");
- } catch (InterruptedException var2) {
- SpringShutdownHook.logger.error(var2.getMessage(), var2);
- }
-
- }
- };
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。