神奇的"僵尸"進程問題(java defunct)
現(xiàn)象描述
- 大概1個月多以前 在啟動腳本中增加了tail -f
- 用來啟動后追蹤日志判斷是否啟動成功
- 后發(fā)現(xiàn)無法執(zhí)行shutdown.sh(卡住 利用curl) 然后無奈使用kill -9
- 但通過ps -el 發(fā)現(xiàn)此時進程變?yōu)閐efunct 即僵尸進程
- 當(dāng)時的解決辦法無奈 只能找到僵尸進程的父進程kill
- 當(dāng)時認為可能是tail的問題 后來啟動腳本中去掉tail 發(fā)現(xiàn)問題解決
- But
- 當(dāng)時一直沒有來得及排查是如何引起僵尸進程的問題
- 這兩天抽時間排查了一下 發(fā)現(xiàn)和tail沒有一毛錢關(guān)系
艱難的排查過程1-嘗試復(fù)現(xiàn)
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();
}
}
}
}
#!/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
- 關(guān)服腳本stop.sh 這里使用kill關(guān)服
#!/bin/bash
pid=`cat pid`
echo $pid
kill $pid
- 分別用兩個腳本測試,得出下面幾個結(jié)論
- start.sh啟動的java進程的父進程是1 即init進程
- start_tail.sh啟動后 java進程的父進程是sh
- sh分別有兩個子進程
- 一個是java子進程 一個是tail子進程
- 當(dāng)啟動start_tail.sh后 因為tail是前臺進程 所以ctrl+c可以結(jié)束
- 此時sh和tail兩個進程都結(jié)束了
- 而此時java進程的父進程變?yōu)榱?
- 用這個例子做各種測試 都無法復(fù)現(xiàn)僵尸進程的問題
- 所以初步結(jié)論是貌似和tail沒有什么關(guān)系
艱難的排查過程2-游戲服務(wù)器嘗試復(fù)現(xiàn)
- 當(dāng)初出現(xiàn)是在游戲服務(wù)器復(fù)現(xiàn)的 那么應(yīng)該比較復(fù)現(xiàn)吧
- 修改了一下一個游戲服務(wù)器的啟動腳本 默認是沒有加tail 現(xiàn)在加上了tail -f
- 啟動游戲服務(wù)器腳本 看到日志 啟動成功 ctrl+c 退出tail
- 調(diào)用shutdown.sh 發(fā)現(xiàn)服務(wù)器順利關(guān)閉
- 結(jié)論
- 竟然無法在游戲服務(wù)器復(fù)現(xiàn)
艱難的排查過程3-各種思考、查閱資料
- 首先從僵尸進程的產(chǎn)生原因入手
- 猜測是否是sh這個父進程沒有調(diào)用waitpid去回收java子進程
- 查詢網(wǎng)上類似的tomcat tail -f問題
- 思考當(dāng)初1個多月以前的情形
- 其中有一個很重要的當(dāng)初情形是shutdown的時候curl卡住了...
- 靈光一現(xiàn)
- 難道是當(dāng)初操作失誤了 沒有按下ctrl+c 而是按下了ctrl+z
神奇的ctrl+z 復(fù)現(xiàn)測試代碼defunct
[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 發(fā)現(xiàn)進程(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 嘗試殺死進程 此時發(fā)現(xiàn)進程已經(jīng)是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命令 從后臺調(diào)到前臺 然后按下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 復(fù)現(xiàn)游戲服務(wù)器defunct
- 啟動腳本(有tail) 等待一段時間(將所有服務(wù)器全部開啟) 并ctrl+z
[xx@achilles spurs-2]$ sh start.sh
......
^Z
[1]+ Stopped sh start.sh
- 此時執(zhí)行shutdown.sh 發(fā)現(xiàn)沒有任何反應(yīng)(卡住) 無奈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
- 此時執(zhí)行jstack 也發(fā)現(xiàn)沒有任何反應(yīng)(卡住) 無奈ctrl+c
[xx@achilles spurs-2]$ jstack 9667
^C
- 此時執(zhí)行kill -9 此時java進程已經(jīng)變?yōu)榱私┦M程
[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將暫停的腳本恢復(fù) 然后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
總結(jié)1
- tail和造成defunct沒有任何關(guān)系
- 根本原因是因為按下ctrl+z 將start_tail.sh切換到了后臺
- 測試1 當(dāng)start_tail.sh后 按下ctrl+z 如果直接被crt#session關(guān)閉了呢
- 更神奇的事情發(fā)生了 java進程直接被干掉了
- ??! 這個在游戲服務(wù)器也測試了 一定要注意!!
- 測試2 執(zhí)行start_tail.sh 直接關(guān)閉ctr#session 則java進程還在 因為是nohup啟動
- 測試3 當(dāng)start_tail.sh后 按下ctrl+z 再按fg 恢復(fù)執(zhí)行 此時之后可以順利shutdown
總結(jié)2
- 正常啟動腳本 沒有tail java進程的父進程是1 即init進程 使用shutdown腳本關(guān)閉java進程后 自動被init進程回收
- 啟動腳本加了tail
- 此時java進程的父進程是sh進程
- sh進程有兩個子進程 一個是java子進程 一個是tail子進程
- 直接ctrl+c 則sh進程和tail進程都結(jié)束 java進程的父進程變?yōu)榱?
- 如果不ctrl+c 直接shutdown java進程 則java進程也會正常結(jié)束 即sh父進程會回收java子進程
總結(jié)3
- 最終'罪魁禍首'是ctrl+z 其會暫停程序的運行
- 如果我們啟動腳本沒有加tail 則執(zhí)行完nohup & 自動到后臺
- 但是我們加了tail后 因為tail是前臺進程 所以要么ctrl+c結(jié)束 要么ctrl+z
- 如果我們按下了ctrl+z 則sh啟動的所有子進程都會暫停
- 所以我們的java進程此時處于暫停狀態(tài) 所以shutdown/jstack都卡住了一樣 只能ctrl+c退出
- 然后錯誤的操作就是使用kill -9 這個會把進程給干掉 但是因為父進程sh被暫停了 所以無法waitPid 執(zhí)行子進程的回收操作 從而導(dǎo)致java進程變?yōu)榱私┦M程
- 而通過fg恢復(fù)后 ctrl+c 父進程和tail都退出 java進程被init進程接管 自動回收
總結(jié)4
- 加tail -f 沒有問題
- 但是一定不要忘了ctrl+c
- 如果ctrl+z 那么一定fg 然后ctrl+c
- 不過當(dāng)出現(xiàn)了shutdown.sh卡住 或者操作jvm都沒反應(yīng) 可以懷疑是暫停了
參考
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。