程序员但凡工作时间久一点,总会遇到一些诡异的事情,比如每当你下班时,服务就挂,然后业务同学就各种找过来了,似乎业务与服务程序就离不开你一样。
而当你登录机器去排查问题时,又发现机器上连进程都没了,心里咯噔一下慌了神,进程咋就消失了?
刚接触Linux的同学,可能都不知道Shell里面前台任务与后台任务的概念,先介绍一下这个,如下:
看个例子:
- # 启动前台进程,然后按Ctrl+z使其变后台进程
- $ java -jar app.jar
- ^Z
- [1]+ Stopped java -jar app.jar
-
- # 查询后台进程,可发现它是Stopped状态
- $ jobs -l
- [1]+ 19316 Stopped java -jar app.jar
-
- # 使用bg后,可以发现任务变Running状态了
- $ bg %1
- [1]+ java -jar app.jar &
- $ jobs
- [1]+ Running java -jar app.jar &
-
- # 再使用fg,会发现任务变成前台任务了
- $ fg %1
-
- # 直接使用&符号,直接就是后台任务并且Running状态
- $ java -jar app.jar &
- [1] 19620
- $ jobs
- [1]+ Running java -jar app.jar &
-
然而,如果你使用上面的方式启动进程,当你下班关掉电脑时,ssh终端会失去连接,你的java进程就会被杀死。
因为在shell里面启动的进程,都是shell这个进程的子进程,ssh失去连接时,shell进程会给它的子进程发SIGHUP信号,这会杀死在shell中启动的子进程,包括后台进程也不例外。
使用nohup命令可以解决这个问题,通过nohup命令启动的进程,会忽略SIGHUP信号,从而让进程能活下来,如下:
- $ nohup java -jar app.jar &
-
如果之前没有使用nohup启动进程,可以使用disown命令使得进程忽略SIGHUP信号,如下:
除了nohup外,还可以使用tmux、screen等伪终端软件,一样可以避免终端断开时进程被杀死,如下:
- # ubuntu安装tmux
- $ sudo apt install tmux
-
- # 新建一个伪终端会话,会话名为app
- $ tmux new -s app
-
- # 启动进程,注意现在已经在tmux伪终端里面了,这和平时操作没什么两样
- # 如果要退出伪终端,先按Ctrl + b再按d,不要使用Ctrl+c
- $ java -jar app.jar
-
- # 列出所有伪终端会话
- $ tmux ls
-
- # 重新attach进入app这个会话,这样就可以看任务运行情况了
- $ tmux attach -t app
-
除了安全启动后台进程外,tmux还可以实现多窗口、分屏等高级功能,感兴趣可以man tmux查看。
我们知道kill可以杀死进程,但其实从原理上来讲,进程并不是被kill杀死的,kill只是给进程发送了一个信号,进程若不捕获信号,内核会执行默认处理程序杀死进程。
通过kill -l可以查看kill命令能够发送的信号,如下:
- $ kill -l
- 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
- 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
- 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
- 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
- 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
- 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
- 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
- 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
- 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
- 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
- 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
- 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
- 63) SIGRTMAX-1 64) SIGRTMAX
-
前面是信号编号,后面是信号名称,可以发现所谓kill -9其实就是向进程发送SIGKILL信号,也可以写成kill -SIGKILL。
如下是常见信号出现的场景:
信号 | 默认行为 | 场景 |
---|---|---|
SIGHUP | 终止进程 | 终端挂断,比如远程ssh断网 |
SIGINT | 终止进程 | 来自键盘的中断,Ctrl+c就是发出此信号 |
SIGQUIT | 终止进程 | 来自键盘的退出,Ctrl+\发此信号 |
SIGKILL | 终止进程 | 杀死程序,kill -9发此信号 |
SIGPIPE | 终止进程 | 向一个没有读用户的管道做写操作,比如Socket网络连接,远端关闭连接后,本端还继续写 |
SIGTERM | 终止进程 | 软件终止信号,kill默认发此信号 |
SIGCHLD | 终止进程 | 一个子进程停止或终止 |
SIGCONT | 终止进程 | 如果进程停止,继续该进程 |
SIGTSTP | 终止进程 | 来自终端的停止信号,Ctrl+z发此信号 |
除了上面的情况外,oom(内存溢出)也是一种常见的进程消失原因,程序中申请了大量的内存,内存不足导致进程死亡,分如下两种情况:
对于java这样的程序,程序使用内存超过-Xmx阈值,jvm会自动退出,并在标准输出流中留下java.lang.OutOfMemoryError异常。
因此启动java进程时,最好使用重定向将标准输出与标准错误保存到日志文件中,然后就可以通过如下方式确认是否oom了:
- # 启动进程时,将标准输出与标准错误保存到日志文件中
- $ nohup java -jar app.jar >stdout.log 2>stderr.log &
-
- # 搜索是否存在OutOfMemoryError异常
- $ grep -A5 'OutOfMemoryError' stdout.log stderr.log
- stdout.log:java.lang.OutOfMemoryError: Java heap space
- stdout.log- at java.base/java.lang.StringCoding.decodeUTF8_0(StringCoding.java:753) ~[na:na]
- stdout.log- at java.base/java.lang.StringCoding.decodeUTF8(StringCoding.java:712) ~[na:na]
- stdout.log- at java.base/java.lang.StringCoding.decode(StringCoding.java:318) ~[na:na]
- stdout.log- at java.base/java.lang.String.<init>(String.java:592) ~[na:na]
- stdout.log- at java.base/java.lang.String.<init>(String.java:614) ~[na:na]
-
这是不熟悉Linux的同学比较容易忽略的一个点,Linux系统如果内存不足了,会使用oom-killer机制找一个内存占用大的进程牺牲掉,而一般Java进程占用内存都挺大,所以它经常被牺牲。
而oom-killer杀死进程时,会打印一些信息在dmesg日志中,因此,可以通过如下方式确认进程消失是否是被oom-killer杀死:
- $ dmesg -T | grep -A10 -i "kill"
- [Fri Dec 17 22:35:47 2021] Memory cgroup out of memory: Killed process 3102186 (java) total-vm:10487368kB, anon-rss:287124kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:3764kB oom_score_adj:0
- [Fri Dec 17 22:35:47 2021] oom_reaper: reaped process 3102186 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
-
注:dmesg相当于是内核日志,内核会记录一些关键信息在其中,如果排查某些问题没有头绪时,养成习惯常规性看一下dmesg的内容,说不定能发现啥呢!