大概1個(gè)月多以前 在啟動(dòng)腳本中增加了tail -f
用來(lái)啟動(dòng)后追蹤日志判斷是否啟動(dòng)成功
后發(fā)現無(wú)法執行shutdown.sh(卡住 利用curl) 然后無(wú)奈使用kill -9
但通過(guò)ps -el 發(fā)現此時(shí)進(jìn)程變?yōu)閐efunct 即僵尸進(jìn)程
當時(shí)的解決辦法無(wú)奈 只能找到僵尸進(jìn)程的父進(jìn)程kill
當時(shí)認為可能是tail的問(wèn)題 后來(lái)啟動(dòng)腳本中去掉tail 發(fā)現問(wèn)題解決
But
當時(shí)一直沒(méi)有來(lái)得及排查是如何引起僵尸進(jìn)程的問(wèn)題
這兩天抽時(shí)間排查了一下 發(fā)現和tail沒(méi)有一毛錢(qián)關(guān)系
測試代碼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(); } } }}啟動(dòng)腳本start.sh
#!/bin/bashnohup java -cp defunct.jar Defunct &echo "$!"echo "$!" > pid
啟動(dòng)腳本start_tail.sh 使用了tail
#!/bin/bashnohup java -cp defunct.jar Defunct &echo "$!"echo "$!" > pidtail -f nohup.out
關(guān)服腳本stop.sh 這里使用kill關(guān)服
#!/bin/bashpid=`cat pid`echo $pidkill $pid
分別用兩個(gè)腳本測試,得出下面幾個(gè)結論
所以初步結論是貌似和tail沒(méi)有什么關(guān)系
此時(shí)sh和tail兩個(gè)進(jìn)程都結束了
而此時(shí)java進(jìn)程的父進(jìn)程變?yōu)榱?
sh分別有兩個(gè)子進(jìn)程
一個(gè)是java子進(jìn)程 一個(gè)是tail子進(jìn)程
start.sh啟動(dòng)的java進(jìn)程的父進(jìn)程是1 即init進(jìn)程
start_tail.sh啟動(dòng)后 java進(jìn)程的父進(jìn)程是sh
當啟動(dòng)start_tail.sh后 因為tail是前臺進(jìn)程 所以ctrl+c可以結束
用這個(gè)例子做各種測試 都無(wú)法復現僵尸進(jìn)程的問(wèn)題
當初出現是在游戲服務(wù)器復現的 那么應該比較復現吧
修改了一下一個(gè)游戲服務(wù)器的啟動(dòng)腳本 默認是沒(méi)有加tail 現在加上了tail -f
啟動(dòng)游戲服務(wù)器腳本 看到日志 啟動(dòng)成功 ctrl+c 退出tail
調用shutdown.sh 發(fā)現服務(wù)器順利關(guān)閉
結論
竟然無(wú)法在游戲服務(wù)器復現
首先從僵尸進(jìn)程的產(chǎn)生原因入手
猜測是否是sh這個(gè)父進(jìn)程沒(méi)有調用waitpid去回收java子進(jìn)程
查詢(xún)網(wǎng)上類(lèi)似的tomcat tail -f問(wèn)題
思考當初1個(gè)多月以前的情形
其中有一個(gè)很重要的當初情形是shutdown的時(shí)候curl卡住了...
靈光一現
難道是當初操作失誤了 沒(méi)有按下ctrl+c 而是按下了ctrl+z
啟動(dòng)start_tail.sh 然后ctrl+z
[xx@achilles deploy_defunct]$ sh start_tail.sh 3974nohup: appending output to `nohup.out'defunct2^Z[2]+ Stopped sh start_tail.sh
啟動(dòng)stop.sh 發(fā)現進(jìn)程(3974)無(wú)法被stop
[xx@achilles deploy_defunct]$ sh stop.sh3974[xx@achilles deploy_defunct]$ jps4146 Jps3974 Defunct212790 SpursLauncher3726 SpursLauncher
使用kill -9 嘗試殺死進(jìn)程 此時(shí)發(fā)現進(jìn)程已經(jīng)是defunct了
[xx@achilles deploy_defunct]$ kill -9 3974[xx@achilles deploy_defunct]$ jps3974 Defunct212790 SpursLauncher4314 Jps3726 SpursLauncher[xx@achilles deploy_defunct]$ ps -el | grep 39740 Z 500 3974 3973 0 80 0 - 0 exit pts/4 00:00:00 java <defunct>
此時(shí) 只要使用fg命令 從后臺調到前臺 然后按下ctrl+c 則僵尸進(jìn)程進(jìn)程自動(dòng)消失
[xx@achilles deploy_defunct]$ ps -el | grep 39740 Z 500 3974 3973 0 80 0 - 0 exit pts/4 00:00:00 java <defunct>[xx@achilles deploy_defunct]$ fgsh start_tail.sh^C[xx@achilles deploy_defunct]$ ps -el | grep 3974
啟動(dòng)腳本(有tail) 等待一段時(shí)間(將所有服務(wù)器全部開(kāi)啟) 并ctrl+z
[xx@achilles spurs-2]$ sh start.sh......^Z[1]+ Stopped sh start.sh
此時(shí)執行shutdown.sh 發(fā)現沒(méi)有任何反應(卡住) 無(wú)奈ctrl+c
[xx@achilles spurs-2]$ sh shutdown.sh ^C[xx@achilles spurs-2]$ jps9667 SpursLauncher9796 Jps[xx@achilles spurs-2]$ ll /proc/9667 | grep cwdlrwxrwxrwx 1 xx xx 0 Dec 5 17:32 cwd -> /data/home/user00/xx/achilles/backend/spurs-2[xx@achilles spurs-2]$ ps -el | grep 96670 T 500 9667 9666 7 80 0 - 1442848 signal pts/6 00:00:07 java[xx@achilles spurs-2]$ ps -el | grep 96660 T 500 9666 8959 0 80 0 - 26521 signal pts/6 00:00:00 sh0 T 500 9667 9666 7 80 0 - 1442848 signal pts/6 00:00:07 java0 T 500 9669 9666 0 80 0 - 25241 signal pts/6 00:00:00 tail
此時(shí)執行jstack 也發(fā)現沒(méi)有任何反應(卡住) 無(wú)奈ctrl+c
[xx@achilles spurs-2]$ jstack 9667^C
此時(shí)執行kill -9 此時(shí)java進(jìn)程已經(jīng)變?yōu)榱私┦M(jìn)程
[xx@achilles spurs-2]$ kill -9 9667[xx@achilles spurs-2]$ ps -el | grep 96670 Z 500 9667 9666 1 80 0 - 0 exit pts/6 00:00:07 java <defunct>
此時(shí)用fg將暫停的腳本恢復 然后ctrl+c 則僵尸進(jìn)程消失 順利被回收
[xx@achilles spurs-2]$ fgsh start.sh^C[xx@achilles spurs-2]$ ps -el | grep 9666[xx@achilles spurs-2]$ ps -el | grep 9667
tail和造成defunct沒(méi)有任何關(guān)系
根本原因是因為按下ctrl+z 將start_tail.sh切換到了后臺
測試1 當start_tail.sh后 按下ctrl+z 如果直接被crt#session關(guān)閉了呢
更神奇的事情發(fā)生了 java進(jìn)程直接被干掉了
??! 這個(gè)在游戲服務(wù)器也測試了 一定要注意!!
測試2 執行start_tail.sh 直接關(guān)閉ctr#session 則java進(jìn)程還在 因為是nohup啟動(dòng)
測試3 當start_tail.sh后 按下ctrl+z 再按fg 恢復執行 此時(shí)之后可以順利shutdown
正常啟動(dòng)腳本 沒(méi)有tail java進(jìn)程的父進(jìn)程是1 即init進(jìn)程 使用shutdown腳本關(guān)閉java進(jìn)程后 自動(dòng)被init進(jìn)程回收
啟動(dòng)腳本加了tail
此時(shí)java進(jìn)程的父進(jìn)程是sh進(jìn)程
sh進(jìn)程有兩個(gè)子進(jìn)程 一個(gè)是java子進(jìn)程 一個(gè)是tail子進(jìn)程
直接ctrl+c 則sh進(jìn)程和tail進(jìn)程都結束 java進(jìn)程的父進(jìn)程變?yōu)榱?
如果不ctrl+c 直接shutdown java進(jìn)程 則java進(jìn)程也會(huì )正常結束 即sh父進(jìn)程會(huì )回收java子進(jìn)程
最終'罪魁禍首'是ctrl+z 其會(huì )暫停程序的運行
如果我們啟動(dòng)腳本沒(méi)有加tail 則執行完nohup & 自動(dòng)到后臺
但是我們加了tail后 因為tail是前臺進(jìn)程 所以要么ctrl+c結束 要么ctrl+z
如果我們按下了ctrl+z 則sh啟動(dòng)的所有子進(jìn)程都會(huì )暫停
所以我們的java進(jìn)程此時(shí)處于暫停狀態(tài) 所以shutdown/jstack都卡住了一樣 只能ctrl+c退出
然后錯誤的操作就是使用kill -9 這個(gè)會(huì )把進(jìn)程給干掉 但是因為父進(jìn)程sh被暫停了 所以無(wú)法waitPid 執行子進(jìn)程的回收操作 從而導致java進(jìn)程變?yōu)榱私┦M(jìn)程
而通過(guò)fg恢復后 ctrl+c 父進(jìn)程和tail都退出 java進(jìn)程被init進(jìn)程接管 自動(dòng)回收
加tail -f 沒(méi)有問(wèn)題
但是一定不要忘了ctrl+c
如果ctrl+z 那么一定fg 然后ctrl+c
不過(guò)當出現了shutdown.sh卡住 或者操作jvm都沒(méi)反應 可以懷疑是暫停
聯(lián)系客服