老闆:kill -9 的原理都不知道就敢去線上執行?明天不用來了!

HollisChuang發表於2020-05-13

GitHub 14.5k Star 的Java工程師成神之路,開放閱讀了!

相信很多程式設計師對於Linux系統都不陌生,即使自己的日常開發機器不是Linux,那麼線上伺服器也大部分都是的,所以,掌握常用的Linux命令也是程式設計師必備的技能。

但是,怕就怕很多人對於部分命令只是一知半解,使用不當就能導致線上故障。

前段時間,我們的線上應用報警,頻繁FGC,需要緊急處理問題,於是有同事去線上重啟機器(正常程式應該是先採集堆dump,然後再重啟,方便排查是否存在記憶體洩露等問題)。

但是在重啟過程中,同事發現正常的重啟命令應用無反應,然後嘗試使用kill命令"殺"掉Java程式,但是仍然無效。於是他私自決定使用 "kill -9"結束了程式的生命。

雖然應用程式被幹掉了,但是隨之而來帶來了很多問題,首先是上游系統突然發生大量報警,對應開發找過來說呼叫我們的RPC服務無響應,頻繁超時。

後來,我們又發現系統中存在部分髒資料,有些在同一個事務中需要完整更新的資料,只跟新了一半...

為什麼正常的kill無法"殺掉"程式,而kill -9就可以?為什麼kill -9會引發這一連串連鎖反應?正常的kill執行時,JVM會如何處理的呢?

要搞清楚這些問題,我們要先從kill命令說起。

kill 命令

我們都知道,想要在Linux中終止一個程式有兩種方式,如果是前臺程式可以使用Ctrl+C鍵進行終止;如果是後臺程式,那麼需要使用kill命令來終止。(其實Ctrl+C也是kill命令)

kill命令的格式是:

kill[引數][程式號]

如:
kill 21121
kill -9 21121

其中[引數]是可選的,程式號可以通過jps/ps/pidof/pstree/top等工具獲取。

kill的命令引數有以下幾種:

-l 訊號,若果不加訊號的編號引數,則使用“-l”引數會列出全部的訊號名稱

-a 當處理當前程式時,不限制命令名和程式號的對應關係

-p 指定kill 命令只列印相關程式的程式號,而不傳送任何訊號

-s 指定傳送訊號

-u 指定使用者

通常情況下,我們使用的-l(訊號)的時候比較多,如我們前文提到的kill -9中的9就是訊號。

訊號如果沒有指定的話,預設會發出終止訊號(15)。常用的訊號如下:

HUP 1 終端斷線

INT 2 中斷(同 Ctrl + C)

QUIT 3 退出(同 Ctrl + \)

TERM 15 終止

KILL 9 強制終止

CONT 18 繼續(與STOP相反, fg/bg命令)

STOP 19 暫停(同 Ctrl + Z)

比較常用的就是強制終止訊號:9終止訊號:15,另外,中斷訊號:2其實就是我們前文提到的Ctrl + C結束前臺程式。

那麼,kill -9kill -15到底有什麼區別呢?該如何選擇呢?

kill -9 和 kill -15的區別

kill命令預設的訊號就是15,首先來說一下這個預設的kill -15訊號。

當使用kill -15時,系統會傳送一個SIGTERM的訊號給對應的程式。當程式接收到該訊號後,具體要如何處理是自己可以決定的。

這時候,應用程式可以選擇:

  • 1、立即停止程式

  • 2、釋放響應資源後停止程式

  • 3、忽略該訊號,繼續執行程式

因為kill -15訊號只是通知對應的程式要進行"安全、乾淨的退出",程式接到訊號之後,退出前一般會進行一些"準備工作",如資源釋放、臨時檔案清理等等,如果準備工作做完了,再進行程式的終止。

但是,如果在"準備工作"進行過程中,遇到阻塞或者其他問題導致無法成功,那麼應用程式可以選擇忽略該終止訊號。

這也就是為什麼我們有的時候使用kill命令是沒辦法"殺死"應用的原因,因為預設的kill訊號是SIGTERM(15),而SIGTERM(15)的訊號是可以被阻塞和忽略的。

kill -15相比,kill -9就相對強硬一點,系統會發出SIGKILL訊號,他要求接收到該訊號的程式應該立即結束執行,不能被阻塞或者忽略。

所以,相比於kill -15命令,kill -9在執行時,應用程式是沒有時間進行"準備工作"的,所以這通常會帶來一些副作用,資料丟失或者終端無法恢復到正常狀態等。

Java是如何處理SIGTERM(15)的

我們都知道,在Linux中,Java應用是作為一個獨立程式執行的,Java程式的終止執行是基於JVM的關閉實現的,JVM關閉方式分為3種:

正常關閉:當最後一個非守護執行緒結束或者呼叫了System.exit或者通過其他特定平臺的方法關閉(接收到SIGINT(2)、SIGTERM(15)訊號等)

強制關閉:通過呼叫Runtime.halt方法或者是在作業系統中強制kill(接收到SIGKILL(9)訊號)

異常關閉:執行中遇到RuntimeException異常等。

JVM程式在接收到kill -15訊號通知的時候,是可以做一些清理動作的,比如刪除臨時檔案等。

當然,開發者也是可以自定義做一些額外的事情的,比如讓tomcat容器停止,讓dubbo服務下線等。

而這種自定義JVM清理動作的方式,是通過JDK中提供的shutdown hook實現的。JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以註冊一個JVM關閉的鉤子。

例子如下:

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...");
    }
}

執行命令:

➜ jps
6520 ShutdownHookTest
6521 Jps
➜ kill 6520

控制檯輸出內容:

hook execute...
Process finished with exit code 143 (interrupted by signal 15: SIGTERM)

可以看到,當我們使用kill(預設kill -15)關閉程式的時候,程式會先執行我註冊的shutdownHook,然後再退出,並且會給出一個提示:interrupted by signal 15: SIGTERM

如果我們執行命令kill -9

➜ kill -9 6520

控制檯輸出內容:

Process finished with exit code 137 (interrupted by signal 9: SIGKILL)

可以看到,當我們使用kill -9 強制關閉程式的時候,程式並沒有執行shutdownHook,而是直接退出了,並且會給出一個提示:interrupted by signal 9: SIGKILL

總結

kill命令用於終止Linux程式,預設情況下,如果不指定訊號,kill 等價於kill -15

kill -15執行時,系統向對應的程式傳送SIGTERM(15)訊號,該訊號是可以被執行、阻塞和忽略的,所以應用程式接收到訊號後,可以做一些準備工作,再進行程式終止。

有的時候,kill -15無法終止程式,因為他可能被忽略,這時候可以使用kill -9,系統會發出SIGKILL(9)訊號,該訊號不允許忽略和阻塞,所以應用程式會立即終止。

這也會帶來很多副作用,如資料丟失等,所以,在非必要時,不要使用kill -9命令,尤其是那些web應用、提供RPC服務、執行定時任務、包含長事務等應用中,因為kill -9 沒給spring容器、tomcat伺服器、dubbo服務、流程引擎、狀態機等足夠的時間進行收尾。

最後,很多人會說,說了這麼多,不是還得用 kill -9 嗎?

其實,本文的目的不是不讓大家用,那就是因噎廢食了。本文是希望大家可以瞭解其背後的原理,知道他可能帶來的副作用。在選擇要不要執行的時候,可以考慮到這些因素,如果能夠針對可能發生的副作用,提前做好預案和心理準備,然後再執行,那就很完美了。

在執行之後,發生了非預期的問題時,大家可以想到有可能和kill -9有關,那本文的目的也算達到了。

歡迎關注我的公眾號,帶給你更多避坑指南.

相關文章