別亂用了,這才是 SpringBoot 停機(jī)的正確方式!?。?/h1>
往期熱門(mén)文章: 1、不好意思, Maven 該換了! 2、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志? 3、基于SpringBoot+MyBatis+Vue的音樂(lè)網(wǎng)站 4、聊聊接口優(yōu)化的幾種方法 5、多線(xiàn)程使用不當(dāng)導(dǎo)致的 OOM
來(lái)源:blog.csdn.net/alex_xfboy/article/details/90404691/
再談為了提醒明知故犯(在一坑里迭倒兩次不是不多見(jiàn)),由于業(yè)務(wù)系統(tǒng)中大量使用了spring Boot embedded tomcat的模式運(yùn)行,在一些運(yùn)維腳本中經(jīng)??吹絃inux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優(yōu)雅停機(jī)。
何為優(yōu)雅關(guān)機(jī)
就是為確保應(yīng)用關(guān)閉時(shí),通知應(yīng)用進(jìn)程釋放所占用的資源
線(xiàn)程池,shutdown(不接受新任務(wù)等待處理完)還是shutdownNow(調(diào)用 Thread.interrupt進(jìn)行中斷) socket 鏈接,比如:netty、mq 告知注冊(cè)中心快速下線(xiàn)(靠心跳機(jī)制客服早都跳起來(lái)了),比如:eureka 清理臨時(shí)文件,比如:poi 各種堆內(nèi)堆外內(nèi)存釋放
總之,進(jìn)程強(qiáng)行終止會(huì)帶來(lái)數(shù)據(jù)丟失或者終端無(wú)法恢復(fù)到正常狀態(tài),在分布式環(huán)境下還可能導(dǎo)致數(shù)據(jù)不一致的情況。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能。
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
kill指令
kill -9 pid 可以模擬了一次系統(tǒng)宕機(jī),系統(tǒng)斷電等極端情況,而kill -15 pid 則是等待應(yīng)用關(guān)閉,執(zhí)行阻塞操作,有時(shí)候也會(huì)出現(xiàn)無(wú)法關(guān)閉應(yīng)用的情況(線(xiàn)上理想情況下,是bug就該尋根溯源)
#查看jvm進(jìn)程pid
jps
#列出所有信號(hào)名稱(chēng)
kill -l
> 基于微服務(wù)的思想,構(gòu)建在 B2C 電商場(chǎng)景下的項(xiàng)目實(shí)戰(zhàn)。核心技術(shù)棧,是 Spring Boot + Dubbo 。未來(lái),會(huì)重構(gòu)成 Spring Cloud Alibaba 。
>
> 項(xiàng)目地址:<https://github.com/YunaiV/onemall>
# Windows下信號(hào)常量值
# 簡(jiǎn)稱(chēng) 全稱(chēng) 數(shù)值
# INT SIGINT 2 Ctrl+C中斷
# ILL SIGILL 4 非法指令
# FPE SIGFPE 8 floating point exception(浮點(diǎn)異常)
# SEGV SIGSEGV 11 segment violation(段錯(cuò)誤)
# TERM SIGTERM 5 Software termination signal from kill(Kill發(fā)出的軟件終止)
# BREAK SIGBREAK 21 Ctrl-Break sequence(Ctrl+Break中斷)
# ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort)
#linux信號(hào)常量值
# 簡(jiǎn)稱(chēng) 全稱(chēng) 數(shù)值
# HUP SIGHUP 1 終端斷線(xiàn)
# INT SIGINT 2 中斷(同 Ctrl + C)
# QUIT SIGQUIT 3 退出(同 Ctrl + \)
# KILL SIGKILL 9 強(qiáng)制終止
# TERM SIGTERM 15 終止
# CONT SIGCONT 18 繼續(xù)(與STOP相反, fg/bg命令)
# STOP SIGSTOP 19 暫停(同 Ctrl + Z)
#....
#可以理解為操作系統(tǒng)從內(nèi)核級(jí)別強(qiáng)行殺死某個(gè)進(jìn)程
kill -9 pid
#理解為發(fā)送一個(gè)通知,等待應(yīng)用主動(dòng)關(guān)閉
kill -15 pid
#也支持信號(hào)常量值全稱(chēng)或簡(jiǎn)寫(xiě)(就是去掉SIG后)
kill -l KILL
思考:jvm是如何接受處理linux信號(hào)量的?
當(dāng)然是在jvm啟動(dòng)時(shí)就加載了自定義SignalHandler,關(guān)閉jvm時(shí)觸發(fā)對(duì)應(yīng)的handle。
public interface SignalHandler {
SignalHandler SIG_DFL = new NativeSignalHandler(0L);
SignalHandler SIG_IGN = new NativeSignalHandler(1L);
void handle(Signal var1);
}
class Terminator {
private static SignalHandler handler = null;
Terminator() {
}
//jvm設(shè)置SignalHandler,在System.initializeSystemClass中觸發(fā)
static void setup() {
if (handler == null) {
SignalHandler var0 = new SignalHandler() {
public void handle(Signal var1) {
Shutdown.exit(var1.getNumber() + 128);//調(diào)用Shutdown.exit
}
};
handler = var0;
try {
Signal.handle(new Signal("INT"), var0);//中斷時(shí)
} catch (IllegalArgumentException var3) {
;
}
try {
Signal.handle(new Signal("TERM"), var0);//終止時(shí)
} catch (IllegalArgumentException var2) {
;
}
}
}
}
Runtime.addShutdownHook
在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);則是為jvm中增加一個(gè)關(guān)閉的鉤子,當(dāng)jvm關(guān)閉的時(shí)候調(diào)用。
public class Runtime {
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
}
class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
}
//它含數(shù)據(jù)結(jié)構(gòu)和邏輯管理虛擬機(jī)關(guān)閉序列
class Shutdown {
/* Shutdown 系列狀態(tài)*/
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static int state = RUNNING;
/* 是否應(yīng)該運(yùn)行所以finalizers來(lái)exit? */
private static boolean runFinalizersOnExit = false;
// 系統(tǒng)關(guān)閉鉤子注冊(cè)一個(gè)預(yù)定義的插槽.
// 關(guān)閉鉤子的列表如下:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
// 當(dāng)前運(yùn)行關(guān)閉鉤子的鉤子的索引
private static int currentRunningHook = 0;
/* 前面的靜態(tài)字段由這個(gè)鎖保護(hù) */
private static class Lock { };
private static Object lock = new Lock();
/* 為native halt方法提供鎖對(duì)象 */
private static Object haltLock = new Lock();
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {//執(zhí)行shutdown過(guò)程中不添加hook
if (state > RUNNING)//如果已經(jīng)在執(zhí)行shutdown操作不能添加hook
throw new IllegalStateException("Shutdown in progress");
} else {//如果hooks已經(jīng)執(zhí)行完畢不能再添加hook。如果正在執(zhí)行hooks時(shí),添加的槽點(diǎn)小于當(dāng)前執(zhí)行的槽點(diǎn)位置也不能添加
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}
/* 執(zhí)行所有注冊(cè)的hooks
*/
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
/* 關(guān)閉JVM的操作
*/
static void halt(int status) {
synchronized (haltLock) {
halt0(status);
}
}
//JNI方法
static native void halt0(int status);
// shutdown的執(zhí)行順序:runHooks > runFinalizersOnExit
private static void sequence() {
synchronized (lock) {
/* Guard against the possibility of a daemon thread invoking exit
* after DestroyJavaVM initiates the shutdown sequence
*/
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
//Runtime.exit時(shí)執(zhí)行,runHooks > runFinalizersOnExit > halt
static void exit(int status) {
boolean runMoreFinalizers = false;
synchronized (lock) {
if (status != 0) runFinalizersOnExit = false;
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and halt */
break;
case FINALIZERS:
if (status != 0) {
/* Halt immediately on nonzero status */
halt(status);
} else {
/* Compatibility with old behavior:
* Run more finalizers and then halt
*/
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
if (runMoreFinalizers) {
runAllFinalizers();
halt(status);
}
synchronized (Shutdown.class) {
/* Synchronize on the class object, causing any other thread
* that attempts to initiate shutdown to stall indefinitely
*/
sequence();
halt(status);
}
}
//shutdown操作,與exit不同的是不做halt操作(關(guān)閉JVM)
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
sequence();
}
}
}
spring 3.2.12
在spring中通過(guò)ContextClosedEvent事件來(lái)觸發(fā)一些動(dòng)作(可以拓展),主要通過(guò)LifecycleProcessor.onClose來(lái)做stopBeans。由此可見(jiàn)spring也基于jvm做了拓展。
public abstract class AbstractApplicationContext extends DefaultResourceLoader {
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread() {
@Override
public void run() {
doClose();
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
//發(fā)布應(yīng)用內(nèi)的關(guān)閉事件
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 停止所有的Lifecycle beans.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// 銷(xiāo)毀spring 的 BeanFactory可能會(huì)緩存單例的 Bean.
destroyBeans();
// 關(guān)閉當(dāng)前應(yīng)用上下文(BeanFactory)
closeBeanFactory();
// 執(zhí)行子類(lèi)的關(guān)閉邏輯
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}
}
public interface LifecycleProcessor extends Lifecycle {
/**
* Notification of context refresh, e.g. for auto-starting components.
*/
void onRefresh();
/**
* Notification of context close phase, e.g. for auto-stopping components.
*/
void onClose();
}
spring boot
到這里就進(jìn)入重點(diǎn)了,spring boot中有spring-boot-starter-actuator 模塊提供了一個(gè) restful 接口,用于優(yōu)雅停機(jī)。執(zhí)行請(qǐng)求 curl -X POST http://127.0.0.1:8088/shutdown ,待關(guān)閉成功則返回提示。
注:線(xiàn)上環(huán)境該url需要設(shè)置權(quán)限,可配合 spring-security使用或在nginx中限制內(nèi)網(wǎng)訪(fǎng)問(wèn)
#啟用shutdown
endpoints.shutdown.enabled=true
#禁用密碼驗(yàn)證
endpoints.shutdown.sensitive=false
#可統(tǒng)一指定所有endpoints的路徑
management.context-path=/manage
#指定管理端口和IP
management.port=8088
management.address=127.0.0.1
#開(kāi)啟shutdown的安全驗(yàn)證(spring-security)
endpoints.shutdown.sensitive=true
#驗(yàn)證用戶(hù)名
security.user.name=admin
#驗(yàn)證密碼
security.user.password=secret
#角色
management.security.role=SUPERUSER
spring boot的shutdown原理也不復(fù)雜,其實(shí)還是通過(guò)調(diào)用AbstractApplicationContext.close實(shí)現(xiàn)的。
@ConfigurationProperties(
prefix = "endpoints.shutdown"
)
public class ShutdownMvcEndpoint extends EndpointMvcAdapter {
public ShutdownMvcEndpoint(ShutdownEndpoint delegate) {
super(delegate);
}
//post請(qǐng)求
@PostMapping(
produces = {"application/vnd.spring-boot.actuator.v1+json", "application/json"}
)
@ResponseBody
public Object invoke() {
return !this.getDelegate().isEnabled() ? new ResponseEntity(Collections.singletonMap("message", "This endpoint is disabled"), HttpStatus.NOT_FOUND) : super.invoke();
}
}
@ConfigurationProperties(
prefix = "endpoints.shutdown"
)
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> implements ApplicationContextAware {
private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "No context to shutdown."));
private static final Map<String, Object> SHUTDOWN_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "Shutting down, bye..."));
private ConfigurableApplicationContext context;
public ShutdownEndpoint() {
super("shutdown", true, false);
}
//執(zhí)行關(guān)閉
public Map<String, Object> invoke() {
if (this.context == null) {
return NO_CONTEXT_MESSAGE;
} else {
boolean var6 = false;
Map var1;
class NamelessClass_1 implements Runnable {
NamelessClass_1() {
}
public void run() {
try {
Thread.sleep(500L);
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
//這個(gè)調(diào)用的就是AbstractApplicationContext.close
ShutdownEndpoint.this.context.close();
}
}
try {
var6 = true;
var1 = SHUTDOWN_MESSAGE;
var6 = false;
} finally {
if (var6) {
Thread thread = new Thread(new NamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
}
}
Thread thread = new Thread(new NamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
return var1;
}
}
}
最近熱文閱讀:
1、不好意思, Maven 該換了! 2、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志? 3、基于SpringBoot+MyBatis+Vue的音樂(lè)網(wǎng)站 4、聊聊接口優(yōu)化的幾種方法 5、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志? 6、為什么不建議使用ON DUPLICATE KEY UPDATE? 7、Java8 Stream,過(guò)分絲滑! 8、8 種最坑SQL語(yǔ)法,工作中踩過(guò)嗎? 9、Java 語(yǔ)言“坑爹” TOP 10 10、你還不明白如何解決分布式Session?看這篇就夠了! 關(guān)注公眾號(hào),你想要的Java都在這里
瀏覽
37
往期熱門(mén)文章: 1、不好意思, Maven 該換了! 2、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志? 3、基于SpringBoot+MyBatis+Vue的音樂(lè)網(wǎng)站 4、聊聊接口優(yōu)化的幾種方法 5、多線(xiàn)程使用不當(dāng)導(dǎo)致的 OOM 來(lái)源:blog.csdn.net/alex_xfboy/article/details/90404691/
再談為了提醒明知故犯(在一坑里迭倒兩次不是不多見(jiàn)),由于業(yè)務(wù)系統(tǒng)中大量使用了spring Boot embedded tomcat的模式運(yùn)行,在一些運(yùn)維腳本中經(jīng)??吹絃inux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優(yōu)雅停機(jī)。
何為優(yōu)雅關(guān)機(jī)
就是為確保應(yīng)用關(guān)閉時(shí),通知應(yīng)用進(jìn)程釋放所占用的資源
線(xiàn)程池,shutdown(不接受新任務(wù)等待處理完)還是shutdownNow(調(diào)用 Thread.interrupt進(jìn)行中斷)socket 鏈接,比如:netty、mq 告知注冊(cè)中心快速下線(xiàn)(靠心跳機(jī)制客服早都跳起來(lái)了),比如:eureka 清理臨時(shí)文件,比如:poi 各種堆內(nèi)堆外內(nèi)存釋放
總之,進(jìn)程強(qiáng)行終止會(huì)帶來(lái)數(shù)據(jù)丟失或者終端無(wú)法恢復(fù)到正常狀態(tài),在分布式環(huán)境下還可能導(dǎo)致數(shù)據(jù)不一致的情況。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能。
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
kill指令
kill -9 pid 可以模擬了一次系統(tǒng)宕機(jī),系統(tǒng)斷電等極端情況,而kill -15 pid 則是等待應(yīng)用關(guān)閉,執(zhí)行阻塞操作,有時(shí)候也會(huì)出現(xiàn)無(wú)法關(guān)閉應(yīng)用的情況(線(xiàn)上理想情況下,是bug就該尋根溯源)
#查看jvm進(jìn)程pid
jps
#列出所有信號(hào)名稱(chēng)
kill -l
> 基于微服務(wù)的思想,構(gòu)建在 B2C 電商場(chǎng)景下的項(xiàng)目實(shí)戰(zhàn)。核心技術(shù)棧,是 Spring Boot + Dubbo 。未來(lái),會(huì)重構(gòu)成 Spring Cloud Alibaba 。
>
> 項(xiàng)目地址:<https://github.com/YunaiV/onemall>
# Windows下信號(hào)常量值
# 簡(jiǎn)稱(chēng) 全稱(chēng) 數(shù)值
# INT SIGINT 2 Ctrl+C中斷
# ILL SIGILL 4 非法指令
# FPE SIGFPE 8 floating point exception(浮點(diǎn)異常)
# SEGV SIGSEGV 11 segment violation(段錯(cuò)誤)
# TERM SIGTERM 5 Software termination signal from kill(Kill發(fā)出的軟件終止)
# BREAK SIGBREAK 21 Ctrl-Break sequence(Ctrl+Break中斷)
# ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort)
#linux信號(hào)常量值
# 簡(jiǎn)稱(chēng) 全稱(chēng) 數(shù)值
# HUP SIGHUP 1 終端斷線(xiàn)
# INT SIGINT 2 中斷(同 Ctrl + C)
# QUIT SIGQUIT 3 退出(同 Ctrl + \)
# KILL SIGKILL 9 強(qiáng)制終止
# TERM SIGTERM 15 終止
# CONT SIGCONT 18 繼續(xù)(與STOP相反, fg/bg命令)
# STOP SIGSTOP 19 暫停(同 Ctrl + Z)
#....
#可以理解為操作系統(tǒng)從內(nèi)核級(jí)別強(qiáng)行殺死某個(gè)進(jìn)程
kill -9 pid
#理解為發(fā)送一個(gè)通知,等待應(yīng)用主動(dòng)關(guān)閉
kill -15 pid
#也支持信號(hào)常量值全稱(chēng)或簡(jiǎn)寫(xiě)(就是去掉SIG后)
kill -l KILL
思考:jvm是如何接受處理linux信號(hào)量的?
當(dāng)然是在jvm啟動(dòng)時(shí)就加載了自定義SignalHandler,關(guān)閉jvm時(shí)觸發(fā)對(duì)應(yīng)的handle。
public interface SignalHandler {
SignalHandler SIG_DFL = new NativeSignalHandler(0L);
SignalHandler SIG_IGN = new NativeSignalHandler(1L);
void handle(Signal var1);
}
class Terminator {
private static SignalHandler handler = null;
Terminator() {
}
//jvm設(shè)置SignalHandler,在System.initializeSystemClass中觸發(fā)
static void setup() {
if (handler == null) {
SignalHandler var0 = new SignalHandler() {
public void handle(Signal var1) {
Shutdown.exit(var1.getNumber() + 128);//調(diào)用Shutdown.exit
}
};
handler = var0;
try {
Signal.handle(new Signal("INT"), var0);//中斷時(shí)
} catch (IllegalArgumentException var3) {
;
}
try {
Signal.handle(new Signal("TERM"), var0);//終止時(shí)
} catch (IllegalArgumentException var2) {
;
}
}
}
}
Runtime.addShutdownHook
在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);則是為jvm中增加一個(gè)關(guān)閉的鉤子,當(dāng)jvm關(guān)閉的時(shí)候調(diào)用。
public class Runtime {
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
}
class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
}
//它含數(shù)據(jù)結(jié)構(gòu)和邏輯管理虛擬機(jī)關(guān)閉序列
class Shutdown {
/* Shutdown 系列狀態(tài)*/
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static int state = RUNNING;
/* 是否應(yīng)該運(yùn)行所以finalizers來(lái)exit? */
private static boolean runFinalizersOnExit = false;
// 系統(tǒng)關(guān)閉鉤子注冊(cè)一個(gè)預(yù)定義的插槽.
// 關(guān)閉鉤子的列表如下:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
// 當(dāng)前運(yùn)行關(guān)閉鉤子的鉤子的索引
private static int currentRunningHook = 0;
/* 前面的靜態(tài)字段由這個(gè)鎖保護(hù) */
private static class Lock { };
private static Object lock = new Lock();
/* 為native halt方法提供鎖對(duì)象 */
private static Object haltLock = new Lock();
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {//執(zhí)行shutdown過(guò)程中不添加hook
if (state > RUNNING)//如果已經(jīng)在執(zhí)行shutdown操作不能添加hook
throw new IllegalStateException("Shutdown in progress");
} else {//如果hooks已經(jīng)執(zhí)行完畢不能再添加hook。如果正在執(zhí)行hooks時(shí),添加的槽點(diǎn)小于當(dāng)前執(zhí)行的槽點(diǎn)位置也不能添加
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}
/* 執(zhí)行所有注冊(cè)的hooks
*/
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
/* 關(guān)閉JVM的操作
*/
static void halt(int status) {
synchronized (haltLock) {
halt0(status);
}
}
//JNI方法
static native void halt0(int status);
// shutdown的執(zhí)行順序:runHooks > runFinalizersOnExit
private static void sequence() {
synchronized (lock) {
/* Guard against the possibility of a daemon thread invoking exit
* after DestroyJavaVM initiates the shutdown sequence
*/
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
//Runtime.exit時(shí)執(zhí)行,runHooks > runFinalizersOnExit > halt
static void exit(int status) {
boolean runMoreFinalizers = false;
synchronized (lock) {
if (status != 0) runFinalizersOnExit = false;
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and halt */
break;
case FINALIZERS:
if (status != 0) {
/* Halt immediately on nonzero status */
halt(status);
} else {
/* Compatibility with old behavior:
* Run more finalizers and then halt
*/
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
if (runMoreFinalizers) {
runAllFinalizers();
halt(status);
}
synchronized (Shutdown.class) {
/* Synchronize on the class object, causing any other thread
* that attempts to initiate shutdown to stall indefinitely
*/
sequence();
halt(status);
}
}
//shutdown操作,與exit不同的是不做halt操作(關(guān)閉JVM)
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
sequence();
}
}
}
spring 3.2.12
在spring中通過(guò)ContextClosedEvent事件來(lái)觸發(fā)一些動(dòng)作(可以拓展),主要通過(guò)LifecycleProcessor.onClose來(lái)做stopBeans。由此可見(jiàn)spring也基于jvm做了拓展。
public abstract class AbstractApplicationContext extends DefaultResourceLoader {
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread() {
@Override
public void run() {
doClose();
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
//發(fā)布應(yīng)用內(nèi)的關(guān)閉事件
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 停止所有的Lifecycle beans.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// 銷(xiāo)毀spring 的 BeanFactory可能會(huì)緩存單例的 Bean.
destroyBeans();
// 關(guān)閉當(dāng)前應(yīng)用上下文(BeanFactory)
closeBeanFactory();
// 執(zhí)行子類(lèi)的關(guān)閉邏輯
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}
}
public interface LifecycleProcessor extends Lifecycle {
/**
* Notification of context refresh, e.g. for auto-starting components.
*/
void onRefresh();
/**
* Notification of context close phase, e.g. for auto-stopping components.
*/
void onClose();
}
spring boot
到這里就進(jìn)入重點(diǎn)了,spring boot中有spring-boot-starter-actuator 模塊提供了一個(gè) restful 接口,用于優(yōu)雅停機(jī)。執(zhí)行請(qǐng)求 curl -X POST http://127.0.0.1:8088/shutdown ,待關(guān)閉成功則返回提示。
注:線(xiàn)上環(huán)境該url需要設(shè)置權(quán)限,可配合 spring-security使用或在nginx中限制內(nèi)網(wǎng)訪(fǎng)問(wèn)
#啟用shutdown
endpoints.shutdown.enabled=true
#禁用密碼驗(yàn)證
endpoints.shutdown.sensitive=false
#可統(tǒng)一指定所有endpoints的路徑
management.context-path=/manage
#指定管理端口和IP
management.port=8088
management.address=127.0.0.1
#開(kāi)啟shutdown的安全驗(yàn)證(spring-security)
endpoints.shutdown.sensitive=true
#驗(yàn)證用戶(hù)名
security.user.name=admin
#驗(yàn)證密碼
security.user.password=secret
#角色
management.security.role=SUPERUSER
spring boot的shutdown原理也不復(fù)雜,其實(shí)還是通過(guò)調(diào)用AbstractApplicationContext.close實(shí)現(xiàn)的。
@ConfigurationProperties(
prefix = "endpoints.shutdown"
)
public class ShutdownMvcEndpoint extends EndpointMvcAdapter {
public ShutdownMvcEndpoint(ShutdownEndpoint delegate) {
super(delegate);
}
//post請(qǐng)求
@PostMapping(
produces = {"application/vnd.spring-boot.actuator.v1+json", "application/json"}
)
@ResponseBody
public Object invoke() {
return !this.getDelegate().isEnabled() ? new ResponseEntity(Collections.singletonMap("message", "This endpoint is disabled"), HttpStatus.NOT_FOUND) : super.invoke();
}
}
@ConfigurationProperties(
prefix = "endpoints.shutdown"
)
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> implements ApplicationContextAware {
private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "No context to shutdown."));
private static final Map<String, Object> SHUTDOWN_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "Shutting down, bye..."));
private ConfigurableApplicationContext context;
public ShutdownEndpoint() {
super("shutdown", true, false);
}
//執(zhí)行關(guān)閉
public Map<String, Object> invoke() {
if (this.context == null) {
return NO_CONTEXT_MESSAGE;
} else {
boolean var6 = false;
Map var1;
class NamelessClass_1 implements Runnable {
NamelessClass_1() {
}
public void run() {
try {
Thread.sleep(500L);
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
//這個(gè)調(diào)用的就是AbstractApplicationContext.close
ShutdownEndpoint.this.context.close();
}
}
try {
var6 = true;
var1 = SHUTDOWN_MESSAGE;
var6 = false;
} finally {
if (var6) {
Thread thread = new Thread(new NamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
}
}
Thread thread = new Thread(new NamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
return var1;
}
}
}
最近熱文閱讀:
1、不好意思, Maven 該換了! 2、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志? 3、基于SpringBoot+MyBatis+Vue的音樂(lè)網(wǎng)站 4、聊聊接口優(yōu)化的幾種方法 5、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志? 6、為什么不建議使用ON DUPLICATE KEY UPDATE? 7、Java8 Stream,過(guò)分絲滑! 8、8 種最坑SQL語(yǔ)法,工作中踩過(guò)嗎? 9、Java 語(yǔ)言“坑爹” TOP 10 10、你還不明白如何解決分布式Session?看這篇就夠了! 關(guān)注公眾號(hào),你想要的Java都在這里
