老板:kill -9的原理都不知道就敢到線上執(zhí)行,明天不用來了!
相信很多程序員對(duì)于Linux系統(tǒng)都不陌生,即使自己的日常開發(fā)機(jī)器不是Linux,那么線上服務(wù)器也大部分都是的,所以,掌握常用的Linux命令也是程序員必備的技能。
但是,怕就怕很多人對(duì)于部分命令只是一知半解,使用不當(dāng)就能導(dǎo)致線上故障。
前段時(shí)間,我們的線上應(yīng)用報(bào)警,頻繁FGC,需要緊急處理問題,于是有同事去線上重啟機(jī)器(正常程序應(yīng)該是先采集堆dump,然后再重啟,方便排查是否存在內(nèi)存泄露等問題)。
但是在重啟過程中,同事發(fā)現(xiàn)正常的重啟命令應(yīng)用無反應(yīng),然后嘗試使用kill命令"殺"掉Java進(jìn)程,但是仍然無效。于是他私自決定使用 "kill -9"結(jié)束了進(jìn)程的生命。
雖然應(yīng)用進(jìn)程被干掉了,但是隨之而來帶來了很多問題,首先是上游系統(tǒng)突然發(fā)生大量報(bào)警,對(duì)應(yīng)開發(fā)找過來說調(diào)用我們的RPC服務(wù)無響應(yīng),頻繁超時(shí)。
后來,我們又發(fā)現(xiàn)系統(tǒng)中存在部分臟數(shù)據(jù),有些在同一個(gè)事務(wù)中需要完整更新的數(shù)據(jù),只更新了一半…
為什么正常的kill無法"殺掉"進(jìn)程,而kill -9就可以?為什么kill -9會(huì)引發(fā)這一連串連鎖反應(yīng)?正常的kill執(zhí)行時(shí),JVM會(huì)如何處理的呢?
要搞清楚這些問題,我們要先從kill命令說起。
kill 命令
我們都知道,想要在Linux中終止一個(gè)進(jìn)程有兩種方式,如果是前臺(tái)進(jìn)程可以使用Ctrl+C鍵進(jìn)行終止;如果是后臺(tái)進(jìn)程,那么需要使用kill命令來終止。(其實(shí)Ctrl+C也是kill命令)
kill命令的格式是:
- kill[參數(shù)][進(jìn)程號(hào)]
- 如:
- kill 21121
- kill -9 21121
其中[參數(shù)]是可選的,進(jìn)程號(hào)可以通過jps/ps/pidof/pstree/top等工具獲取。
kill的命令參數(shù)有以下幾種:
- -l 信號(hào),若果不加信號(hào)的編號(hào)參數(shù),則使用“-l”參數(shù)會(huì)列出全部的信號(hào)名稱
- -a 當(dāng)處理當(dāng)前進(jìn)程時(shí),不限制命令名和進(jìn)程號(hào)的對(duì)應(yīng)關(guān)系
- -p 指定kill 命令只打印相關(guān)進(jìn)程的進(jìn)程號(hào),而不發(fā)送任何信號(hào)
- -s 指定發(fā)送信號(hào)
- -u 指定用戶
通常情況下,我們使用的-l(信號(hào))的時(shí)候比較多,如我們前文提到的kill -9中的9就是信號(hào)。
信號(hào)如果沒有指定的話,默認(rèn)會(huì)發(fā)出終止信號(hào)(15)。常用的信號(hào)如下:
- HUP 1 終端斷線
- INT 2 中斷(同 Ctrl + C)
- QUIT 3 退出(同 Ctrl + \)
- TERM 15 終止
- KILL 9 強(qiáng)制終止
- CONT 18 繼續(xù)(與STOP相反, fg/bg命令)
- STOP 19 暫停(同 Ctrl + Z)
比較常用的就是強(qiáng)制終止信號(hào):9和終止信號(hào):15,另外,中斷信號(hào):2其實(shí)就是我們前文提到的Ctrl + C結(jié)束前臺(tái)進(jìn)程。
那么,kill -9 和 kill -15到底有什么區(qū)別呢?該如何選擇呢?
kill -9 和 kill -15的區(qū)別
kill命令默認(rèn)的信號(hào)就是15,首先來說一下這個(gè)默認(rèn)的kill -15信號(hào)。
當(dāng)使用kill -15時(shí),系統(tǒng)會(huì)發(fā)送一個(gè)SIGTERM的信號(hào)給對(duì)應(yīng)的程序。當(dāng)程序接收到該信號(hào)后,具體要如何處理是自己可以決定的。
這時(shí)候,應(yīng)用程序可以選擇:
- 1、立即停止程序
- 2、釋放響應(yīng)資源后停止程序
- 3、忽略該信號(hào),繼續(xù)執(zhí)行程序
因?yàn)閗ill -15信號(hào)只是通知對(duì)應(yīng)的進(jìn)程要進(jìn)行"安全、干凈的退出",程序接到信號(hào)之后,退出前一般會(huì)進(jìn)行一些"準(zhǔn)備工作",如資源釋放、臨時(shí)文件清理等等,如果準(zhǔn)備工作做完了,再進(jìn)行程序的終止。
但是,如果在"準(zhǔn)備工作"進(jìn)行過程中,遇到阻塞或者其他問題導(dǎo)致無法成功,那么應(yīng)用程序可以選擇忽略該終止信號(hào)。
這也就是為什么我們有的時(shí)候使用kill命令是沒辦法"殺死"應(yīng)用的原因,因?yàn)槟J(rèn)的kill信號(hào)是SIGTERM(15),而SIGTERM(15)的信號(hào)是可以被阻塞和忽略的。
和kill -15相比,kill -9就相對(duì)強(qiáng)硬一點(diǎn),系統(tǒng)會(huì)發(fā)出SIGKILL信號(hào),他要求接收到該信號(hào)的程序應(yīng)該立即結(jié)束運(yùn)行,不能被阻塞或者忽略。所以,相比于kill -15命令,kill -9在執(zhí)行時(shí),應(yīng)用程序是沒有時(shí)間進(jìn)行"準(zhǔn)備工作"的,所以這通常會(huì)帶來一些副作用,數(shù)據(jù)丟失或者終端無法恢復(fù)到正常狀態(tài)等。
Java是如何處理SIGTERM(15)的
我們都知道,在Linux中,Java應(yīng)用是作為一個(gè)獨(dú)立進(jìn)程運(yùn)行的,Java程序的終止運(yùn)行是基于JVM的關(guān)閉實(shí)現(xiàn)的,JVM關(guān)閉方式分為3種:
正常關(guān)閉:當(dāng)最后一個(gè)非守護(hù)線程結(jié)束或者調(diào)用了System.exit或者通過其他特定平臺(tái)的方法關(guān)閉(接收到SIGINT(2)、SIGTERM(15)信號(hào)等)強(qiáng)制關(guān)閉:通過調(diào)用Runtime.halt方法或者是在操作系統(tǒng)中強(qiáng)制kill(接收到SIGKILL(9)信號(hào))異常關(guān)閉:運(yùn)行中遇到RuntimeException異常等。
JVM進(jìn)程在接收到kill -15信號(hào)通知的時(shí)候,是可以做一些清理動(dòng)作的,比如刪除臨時(shí)文件等。當(dāng)然,開發(fā)者也是可以自定義做一些額外的事情的,比如讓tomcat容器停止,讓dubbo服務(wù)下線等。而這種自定義JVM清理動(dòng)作的方式,是通過JDK中提供的shutdown hook實(shí)現(xiàn)的。JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注冊(cè)一個(gè)JVM關(guān)閉的鉤子。例子如下:
- package com.hollis;
- public class ShutdownHookTest {
- public static void main(String[] args) {
- boolean flag = true;
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- System.out.println("hook execute...");
- }));
- while (flag) {
- // app is runing
- }
- System.out.println("main thread execute end...");
- }
- }
執(zhí)行命令:
- ➜ jps
- 6520 ShutdownHookTest
- 6521 Jps
- ➜ kill 6520
控制臺(tái)輸出內(nèi)容:
- hook execute...
- Process finished with exit code 143 (interrupted by signal 15: SIGTERM)
可以看到,當(dāng)我們使用kill(默認(rèn)kill -15)關(guān)閉進(jìn)程的時(shí)候,程序會(huì)先執(zhí)行我注冊(cè)的shutdownHook,然后再退出,并且會(huì)給出一個(gè)提示:interrupted by signal 15: SIGTERM如果我們執(zhí)行命令kill -9:
- ➜ kill -9 6520
控制臺(tái)輸出內(nèi)容:
- Process finished with exit code 137 (interrupted by signal 9: SIGKILL)
可以看到,當(dāng)我們使用kill -9 強(qiáng)制關(guān)閉進(jìn)程的時(shí)候,程序并沒有執(zhí)行shutdownHook,而是直接退出了,并且會(huì)給出一個(gè)提示:interrupted by signal 9: SIGKILL
總結(jié)
kill命令用于終止Linux進(jìn)程,默認(rèn)情況下,如果不指定信號(hào),kill 等價(jià)于kill -15。
kill -15執(zhí)行時(shí),系統(tǒng)向?qū)?yīng)的程序發(fā)送SIGTERM(15)信號(hào),該信號(hào)是可以被執(zhí)行、阻塞和忽略的,所以應(yīng)用程序接收到信號(hào)后,可以做一些準(zhǔn)備工作,再進(jìn)行程序終止。
有的時(shí)候,kill -15無法終止程序,因?yàn)樗赡鼙缓雎?,這時(shí)候可以使用kill -9,系統(tǒng)會(huì)發(fā)出SIGKILL(9)信號(hào),該信號(hào)不允許忽略和阻塞,所以應(yīng)用程序會(huì)立即終止。
這也會(huì)帶來很多副作用,如數(shù)據(jù)丟失等,所以,在非必要時(shí),不要使用kill -9命令,尤其是那些web應(yīng)用、提供RPC服務(wù)、執(zhí)行定時(shí)任務(wù)、包含長(zhǎng)事務(wù)等應(yīng)用中,因?yàn)閗ill -9 沒給spring容器、tomcat服務(wù)器、dubbo服務(wù)、流程引擎、狀態(tài)機(jī)等足夠的時(shí)間進(jìn)行收尾。
關(guān)于作者:Hollis(ID:hollischuang),一個(gè)對(duì)Coding有著獨(dú)特追求的人,現(xiàn)任阿里巴巴技術(shù)專家,個(gè)人技術(shù)博主,技術(shù)文章全網(wǎng)閱讀量數(shù)千萬,《程序員的三門課》聯(lián)合作者。
【本文是51CTO專欄作者Hollis的原創(chuàng)文章,作者微信公眾號(hào)Hollis(ID:hollischuang)】