神奇的Java'僵尸'進程問題

神奇的"僵尸"進程問題(java defunct)

現象描述

  • 大概1個月多以前 在啟動腳本中增加了tail -f
  • 用來啟動后追蹤日志判斷是否啟動成功
  • 后發現無法執行shutdown.sh(卡住 利用curl) 然后無奈使用kill -9
  • 但通過ps -el 發現此時進程變為defunct 即僵尸進程
  • 當時的解決辦法無奈 只能找到僵尸進程的父進程kill
  • 當時認為可能是tail的問題 后來啟動腳本中去掉tail 發現問題解決
  • But
    • 當時一直沒有來得及排查是如何引起僵尸進程的問題
    • 這兩天抽時間排查了一下 發現和tail沒有一毛錢關系

艱難的排查過程1-嘗試復現

  • 測試代碼Defunct.java
import java.util.concurrent.TimeUnit;

public class Defunct {
    public static void main(String[] args) {
        while (true) {
            System.out.println("test defunct");

            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 啟動腳本start.sh
#!/bin/bash

nohup java -cp defunct.jar Defunct &

echo "$!"
echo "$!" > pid
  • 啟動腳本start_tail.sh 使用了tail
#!/bin/bash

nohup java -cp defunct.jar Defunct &

echo "$!"
echo "$!" > pid

tail -f nohup.out

  • 關服腳本stop.sh 這里使用kill關服
#!/bin/bash

pid=`cat pid`
echo $pid

kill $pid
  • 分別用兩個腳本測試,得出下面幾個結論
    • start.sh啟動的java進程的父進程是1 即init進程
    • start_tail.sh啟動后 java進程的父進程是sh
      • sh分別有兩個子進程
      • 一個是java子進程 一個是tail子進程
    • 當啟動start_tail.sh后 因為tail是前臺進程 所以ctrl+c可以結束
      • 此時sh和tail兩個進程都結束了
      • 而此時java進程的父進程變為了1
    • 用這個例子做各種測試 都無法復現僵尸進程的問題
      • 所以初步結論是貌似和tail沒有什么關系

艱難的排查過程2-游戲服務器嘗試復現

  • 當初出現是在游戲服務器復現的 那么應該比較復現吧
  • 修改了一下一個游戲服務器的啟動腳本 默認是沒有加tail 現在加上了tail -f
  • 啟動游戲服務器腳本 看到日志 啟動成功 ctrl+c 退出tail
  • 調用shutdown.sh 發現服務器順利關閉
  • 結論
    • 竟然無法在游戲服務器復現

艱難的排查過程3-各種思考、查閱資料

  • 首先從僵尸進程的產生原因入手
    • 猜測是否是sh這個父進程沒有調用waitpid去回收java子進程
  • 查詢網上類似的tomcat tail -f問題
  • 思考當初1個多月以前的情形
    • 其中有一個很重要的當初情形是shutdown的時候curl卡住了...
  • 靈光一現
    • 難道是當初操作失誤了 沒有按下ctrl+c 而是按下了ctrl+z

神奇的ctrl+z 復現測試代碼defunct

  • 啟動start_tail.sh 然后ctrl+z
[xx@achilles deploy_defunct]$ sh start_tail.sh 
3974
nohup: appending output to `nohup.out'
defunct2
^Z
[2]+  Stopped                 sh start_tail.sh
  • 啟動stop.sh 發現進程(3974)無法被stop
[xx@achilles deploy_defunct]$ sh stop.sh
3974
[xx@achilles deploy_defunct]$ jps
4146 Jps
3974 Defunct2
12790 SpursLauncher
3726 SpursLauncher
  • 使用kill -9 嘗試殺死進程 此時發現進程已經是defunct了
[xx@achilles deploy_defunct]$ kill -9 3974
[xx@achilles deploy_defunct]$ jps
3974 Defunct2
12790 SpursLauncher
4314 Jps
3726 SpursLauncher
[xx@achilles deploy_defunct]$ ps -el | grep 3974
0 Z   500  3974  3973  0  80   0 -     0 exit   pts/4    00:00:00 java <defunct>
  • 此時 只要使用fg命令 從后臺調到前臺 然后按下ctrl+c 則僵尸進程進程自動消失
[xx@achilles deploy_defunct]$ ps -el | grep 3974
0 Z   500  3974  3973  0  80   0 -     0 exit   pts/4    00:00:00 java <defunct>
[xx@achilles deploy_defunct]$ fg
sh start_tail.sh
^C
[xx@achilles deploy_defunct]$ ps -el | grep 3974

神奇的ctrl+z 復現游戲服務器defunct

  • 啟動腳本(有tail) 等待一段時間(將所有服務器全部開啟) 并ctrl+z
[xx@achilles spurs-2]$ sh start.sh
......
^Z
[1]+  Stopped                 sh start.sh
  • 此時執行shutdown.sh 發現沒有任何反應(卡住) 無奈ctrl+c
[xx@achilles spurs-2]$ sh shutdown.sh 
^C
[xx@achilles spurs-2]$ jps
9667 SpursLauncher
9796 Jps
[xx@achilles spurs-2]$ ll /proc/9667 | grep cwd
lrwxrwxrwx  1 xx xx 0 Dec  5 17:32 cwd -> /data/home/user00/xx/achilles/backend/spurs-2

[xx@achilles spurs-2]$ ps -el | grep 9667
0 T   500  9667  9666  7  80   0 - 1442848 signal pts/6  00:00:07 java
[xx@achilles spurs-2]$ ps -el | grep 9666
0 T   500  9666  8959  0  80   0 - 26521 signal pts/6    00:00:00 sh
0 T   500  9667  9666  7  80   0 - 1442848 signal pts/6  00:00:07 java
0 T   500  9669  9666  0  80   0 - 25241 signal pts/6    00:00:00 tail
  • 此時執行jstack 也發現沒有任何反應(卡住) 無奈ctrl+c
[xx@achilles spurs-2]$ jstack 9667
^C
  • 此時執行kill -9 此時java進程已經變為了僵尸進程
[xx@achilles spurs-2]$ kill -9 9667
[xx@achilles spurs-2]$ ps -el | grep 9667
0 Z   500  9667  9666  1  80   0 -     0 exit   pts/6    00:00:07 java <defunct>
  • 此時用fg將暫停的腳本恢復 然后ctrl+c 則僵尸進程消失 順利被回收
[xx@achilles spurs-2]$ fg
sh start.sh
^C
[xx@achilles spurs-2]$ ps -el | grep 9666
[xx@achilles spurs-2]$ ps -el | grep 9667

總結1

  • tail和造成defunct沒有任何關系
  • 根本原因是因為按下ctrl+z 將start_tail.sh切換到了后臺
  • 測試1 當start_tail.sh后 按下ctrl+z 如果直接被crt#session關閉了呢
    • 更神奇的事情發生了 java進程直接被干掉了
    • ??! 這個在游戲服務器也測試了 一定要注意!!
  • 測試2 執行start_tail.sh 直接關閉ctr#session 則java進程還在 因為是nohup啟動
  • 測試3 當start_tail.sh后 按下ctrl+z 再按fg 恢復執行 此時之后可以順利shutdown

總結2

  • 正常啟動腳本 沒有tail java進程的父進程是1 即init進程 使用shutdown腳本關閉java進程后 自動被init進程回收
  • 啟動腳本加了tail
    • 此時java進程的父進程是sh進程
    • sh進程有兩個子進程 一個是java子進程 一個是tail子進程
    • 直接ctrl+c 則sh進程和tail進程都結束 java進程的父進程變為了1
    • 如果不ctrl+c 直接shutdown java進程 則java進程也會正常結束 即sh父進程會回收java子進程

總結3

  • 最終'罪魁禍首'是ctrl+z 其會暫停程序的運行
  • 如果我們啟動腳本沒有加tail 則執行完nohup & 自動到后臺
  • 但是我們加了tail后 因為tail是前臺進程 所以要么ctrl+c結束 要么ctrl+z
  • 如果我們按下了ctrl+z 則sh啟動的所有子進程都會暫停
  • 所以我們的java進程此時處于暫停狀態 所以shutdown/jstack都卡住了一樣 只能ctrl+c退出
  • 然后錯誤的操作就是使用kill -9 這個會把進程給干掉 但是因為父進程sh被暫停了 所以無法waitPid 執行子進程的回收操作 從而導致java進程變為了僵尸進程
  • 而通過fg恢復后 ctrl+c 父進程和tail都退出 java進程被init進程接管 自動回收

總結4

  • 加tail -f 沒有問題
  • 但是一定不要忘了ctrl+c
  • 如果ctrl+z 那么一定fg 然后ctrl+c
  • 不過當出現了shutdown.sh卡住 或者操作jvm都沒反應 可以懷疑是暫停了

參考

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容