写这篇文章是因为看到 "线程池在使用结束后应该正确关闭." 那么如果我们的 Spring 应用都无法正确关闭, 那么线程池肯定也无从保障
1. 优雅关闭
- kill with pid, without
-9
大多数情况下无须在意这个问题, 正确使用 kill
命令关闭就行 (注意不能使用 kill -9
)
java
kill $(cat ./application.pid)
默认发送的信号是
SIGTERM
,其信号编号为15
. 在无法正常终止进程时使用-9
(SIGKILL): 强制立即终止进程。这个信号无法被捕获或忽略,进程会被操作系统强制杀死
- 在启动的时候, 将 pid 和 端口号 记录到文件中 (这些文件在程序关闭后会自动删除)
java
public static void main(String[] args) {
try {
long start = System.currentTimeMillis();
SpringApplicationBuilder builder = new SpringApplicationBuilder(App.class);
builder.beanNameGenerator(FullyQualifiedAnnotationBeanNameGenerator.INSTANCE);
// startup with pid file and port file
builder.listeners(new ApplicationPidFileWriter()); // pid file
// 上面的类可以传入文件路径, 方便使用 kill 命令
// builder.listeners(new ApplicationPidFileWriter("./bin/shutdown.pid")); // kill $(cat ./bin/shutdown.pid)
builder.listeners(new WebServerPortFileWriter()); // port file
builder.run(args);
long costs = (System.currentTimeMillis() - start) / 1000;
log.info("### APP STARTED ### It take {} seconds.", costs);
} catch (Exception e) { log.error(" STARTUP FAILED ", e); }
}
2. 增加 URL 接口来关闭程序
- 使用
ConfigurableApplicationContext#close
- 使用
int exit = SpringApplication.exit(context);
- 关闭应用时执行某段逻辑
- 注解
@PreDestroy
- 事件
ContextClosedEvent
- 注解
java
@Slf4j
@RestController
public class ShutDownController {
@Autowired
private ConfigurableApplicationContext applicationContext;
@Autowired
private ApplicationContext context; // 两个 context 都行
@GetMapping("/shutdown")
public String shutdown() {
log.error("1. shutdown begin..."); // 前面的编号是执行顺序
// 清理资源并关闭
((ConfigurableApplicationContext) context).close();
// applicationContext.close();
log.error("3. spring application closed.");
// 在嵌入式应用或在某些情况下,关闭上下文后,JVM 可能不会自动退出。为了确保应用完全终止,使用 System.exit(0)
System.exit(0); // not necessary
// log.error("system closed."); // 这里日志对象 log 已经失效, 无法打印, 换用 println
System.err.println("system closed.");
return "shutdown";
}
@GetMapping("/exit")
public String exit() {
log.error("1. shutdown begin...");
// 负责清理资源
int exit = SpringApplication.exit(context);
log.error("3. Spring app closed.");
System.exit(exit); // not necessary
// log.error("system closed."); // It will fail over
System.err.println("system closed.");
return "shutdown";
}
@PreDestroy // @PreDestroy 注解: 在 Spring 应用销毁前, 执行某段逻辑
public void onDestroy() throws Exception {
log.error("3. Spring Container is destroyed!");
}
@Slf4j
@Component // ContextClosedEvent 事件: 在 Spring 上下文关闭时机, 执行某段逻辑
public static class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.error("2. graceful shutdown (ContextClosedEvent)");
}
}
}
3. 优雅启动
- 显示配置参数
java
/**
* 显示配置参数
*/
public static void printEnvironment(Environment environment) {
try {
log.info("******************JVM参数**********************");
String[] properties =
{"java.home", "java.version", "java.vm.name", "java.vm.vendor", "os.name", "user.dir"};
for (String property : properties) {
log.info("* {} = {}", property, System.getProperty(property));
}
MemoryMXBean bean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = bean.getHeapMemoryUsage();
log.info("* 初始内存 = " + heapUsage.getInit() / 1024 / 1024 + "M");
log.info("* 已使用内存 = " + heapUsage.getUsed() / 1024 / 1024 + "M");
log.info("* 已提交内存 = " + heapUsage.getCommitted() / 1024 / 1024 + "M");
log.info("* 最大内存 = " + heapUsage.getMax() / 1024 / 1024 + "M");
} catch (Exception e) {
log.error("异常: {}", e.getMessage());
}
}
- 启动时调用:
printEnvironment(builder.context().getEnvironment());
4. 线程池的关闭
-
由 Spring 管理的线程池, 关闭 Spring 应用上下文时会自动关闭, 包括
@Async
或 通过 Spring 配置的线程池 -
自定义线程池 (未通过 Spring 管理), 在关闭应用时, 需要显式地调用
shutdown()
来确保任务能够正常完成, 避免丢失. 可以利用上面提到的, 关闭应用时执行某段逻辑的方式执行- 注解
@PreDestroy
- 事件
ContextClosedEvent
- 注解
(END)