Loading

Linux命令拾遗-我的进程消失了

原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。

简介

程序员但凡工作时间久一点,总会遇到一些诡异的事情,比如每当你下班时,服务就挂,然后业务同学就各种找过来了,似乎业务与服务程序就离不开你一样。
而当你登录机器去排查问题时,又发现机器上连进程都没了,心里咯噔一下慌了神,进程咋就消失了?

后台任务

刚接触Linux的同学,可能都不知道Shell里面前台任务与后台任务的概念,先介绍一下这个,如下:
jobs

  1. 命令直接执行时,启动的是前台任务,而在命令后面添加&符号,可让进程启动为后台任务。
  2. 使用jobs可以查询当前shell下启动的所有后台任务。
  3. 如果进程是前台任务,按下Ctrl+z可让其变成后台任务,但任务同时会变成暂停状态。
  4. 使用bg可以使暂停状态的任务变成运行状态。
  5. 使用fg可以使后台任务变成前台任务,如果任务是暂停状态,会使其变为运行状态。

看个例子:

# 启动前台进程,然后按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 &

nohup与disown

然而,如果你使用上面的方式启动进程,当你下班关掉电脑时,ssh终端会失去连接,你的java进程就会被杀死。
因为在shell里面启动的进程,都是shell这个进程的子进程,ssh失去连接时,shell进程会给它的子进程发SIGHUP信号,这会杀死在shell中启动的子进程,包括后台进程也不例外。

使用nohup命令可以解决这个问题,通过nohup命令启动的进程,会忽略SIGHUP信号,从而让进程能活下来,如下:

$ nohup java -jar app.jar &

如果之前没有使用nohup启动进程,可以使用disown命令使得进程忽略SIGHUP信号,如下:

  1. 使用Ctrl+z使之到后台运行,同时进程会变成暂停状态。
  2. 使用jobs -l查询刚才转到后台任务的jobid,比如是1,使用bg %1使之变成运行状态。
  3. 使用disown -h %1使这个后台任务忽略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只是给进程发送了一个信号,进程若不捕获信号,内核会执行默认处理程序杀死进程。
通过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导致的进程消失

除了上面的情况外,oom(内存溢出)也是一种常见的进程消失原因,程序中申请了大量的内存,内存不足导致进程死亡,分如下两种情况:

  1. jvm内存不足导致进程死亡

对于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]
  1. oom-killer机制导致进程死亡

这是不熟悉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的内容,说不定能发现啥呢!

本系列文章索引
Linux命令拾遗-入门篇
Linux命令拾遗-文本处理篇
Linux命令拾遗-软件资源观测
Linux命令拾遗-硬件资源观测
Linux命令拾遗-剖析工具
Linux命令拾遗-动态追踪工具
Linux命令拾遗-理解系统负载
Linux命令拾遗-top中的%nice是啥
Linux命令拾遗-网络抓包工具

往期内容

Linux命令拾遗-入门篇
原来awk真是神器啊
Linux文本命令技巧(上)
Linux文本命令技巧(下)
字符编码解惑

posted @ 2022-03-26 20:30  扣钉日记  阅读(878)  评论(0编辑  收藏  举报