进程无端消失的破案历程

概述

前段时间公司有个系统的进程总是无端退出,在客户那边好好的,在家里服务器上总是出现,并且出现的时间也没啥规律,固然最终查出来仍是有规律的,不过这个规律比较特别。你们看了后面的内容以后就明白了,真的很特殊!java

初步分析 进程Crash?

当同事找到个人时候,我第一反应是是否是进程Crash了,若是是crash,那一般状况下会有crash的日志,检查了一遍,什么日志都没有留下,固然有时候Crash了,JVM也确实也不会留下Crash日志,不过这个特别罕见了,绝大部分是人为操做了。shell

被OS Kill?

既然不是Crash,那是否是系统存在内存泄露,被OS Kill了,不过很快经过dmesg也排除了,由于没有看到任何kill的迹象。服务器

System.exit?

排除掉以上两个因素以后,接着立刻就怀疑是否有什么代码执行过System.exit,因而从新编译了一把JDK,在System的exit方法处打印了些日志,因而坐等奇迹的发生。微信

使人兴奋的是,进程真的消失了,但是使人遗憾的是,咱们埋点的日志并无出现。这让我再次陷入沉思。session

回归源码

从日志看确实是调用了ShutdownHook
image.png函数

因而找到addShutdownHook源码的位置
image.png工具

再次翻了一下JDK的源码,除了正常退出,System.exit等以外还有哪些状况会调用这个Shutdown的Hook,因而将埋点埋到了Shutdown.runHooks方法里。
image.pngspa

继续等待事情再次发生,果真没多久,当天下午又发生了,打印了以下日志
image.png日志

也就是说当时是收到了一个SIGHUP的信号,这个信号最终会让进程退出,JVM对这个信号确实是没有特殊处理的,所以没有咱们看到的Crash日志。code

那接下来就是要找到为何会收到这个信号了,这个信号又是谁发出的。

找出信号源

SIGHUP这个信号,最主要发生的场景是Shell终端关闭一个Session的时候会对该Session关联的进程发送一个SIGHUP信号,这个信号默认是会退出进程的。为此我还特意下载了ITerm2(我和同事都是mac,用的iTerm2的终端)的源码,还真找到了几个发送SIGHUP信号的代码
image.png

看名字PTYTask就能猜到了,这应该是一个seesion任务,因而进到代码里看到主要是有两个方法有发送SIGHUP信号给子进程,分别是dealloc和stop,其中stop会经过sendSignal函数给子进程发送SIGHUP信号。
image.png
对于我这种比较喜欢自虐的人,通常会想怎么就肯定这个就是我要找的代码,以前从没有写过Object-C的代码,想着是否有个相似java的jmap的工具可让我看内存里的对象的状况,然而临时没找到,不过偶然发现mac自带的Activity工具就能看到一些迹象,因而在Activity里找到了iTerm2进程,而后对其内存数据进行了采样,看可否抓到相似PTYTask. dealloc或者stop的调用栈,但是比较难模拟,由于存在时间差,点击采样的时候,很快就结束了,我还没来得及关闭session。在看采样报告的时候偶然看到了/usr/bin/sample的命令,原来Activity是采用这个命令进行采样的,因而摸索了一把,真能搞起来,采样的时间能够自定义,间隔是1ms一次,这样可让我有充足的时间来操做了,因而在采样开启以后,不断地开一个session,起一个进程,而后close,重复作了好几回,结束采样以后看采样的输出,还真的就抓到了PTYTask.stop的调用栈
image.png

这也就验证了我close session的时候确实会给对应的子进程发送SIGHUP的信号。

那到此为止咱们能够确认的是

  • 进程退出是由于收到了信号SIGHUP
  • 而SIGHUP的发生是由于终端Session Close

那究竟是不是这种状况呢?

从新复盘问题现场

这个貌似不太可能,由于咱们的shell脚本里执行java的时候都会带上&,这样进程就会后台运行,不会出现这种session的问题。看了下脚本,确实是带上了&的,本身也模拟了几遍,在一个shell里调用带有&的java命令,关闭终端java进程并不会退出。难道是由于同事的终端配置和个人不同?后来要同事发了它的iTerm2里的seesion的配置给我看了下,和个人彻底同样,这就挺奇怪了。

因而再回过来看看以前的几回进程消失时候的日志(我要同事有保留),看到那几个进程退出的时间点,分别是

  • 2019-01-14 20:42:52 2019-01-15 18:34:00 2019-01-18 00:57:58* 2019-01-18 17:34:30

这几个时间点彻底没有规律看起来,不过我忽然想起2019-01-18那天是周五,同事是在上海出差,而17:34:30那会应该是快下班了,或者那会正好从上海赶车回杭州,因而问了下那个点同事是否是准备回杭州了,同事说那会确实合上电脑准备赶车回杭州,因而我接着问其余几个时间是否是差很少也是合上电脑的时间,结果还真是那么巧,既然这样,那说明咱们的判断方向是正确的了。

水落石出

后面忽然发现同事修改过这个启动脚本,在脚本的最后加了行命令

java xxxx &cd $DIR_LOG && tail -f common-*.log

难道是这个命令搞的鬼?

忽然想起一个问题来,父子进程的问题,假如说当咱们在shell里加了&跑的话,当父进程跑完以后,会直接将其挂到init进程下面,也就是你经过ps -ef去看它的父进程会是1号进程,可是若是原来的父进程没有跑完,那其父进程仍然会是原来的进程,因此当咱们执行这个脚本的时候,一直被执行tail等待日志输出,这样一来,若是咱们将当前窗口关闭了,那就意味着父进程会退出,而后给子进程发送SIGHUP的信号,因而致使java进程退出,可是若是咱们在tail日志过程当中ctrl c了,那java进程会过继给init进程,所以此时当咱们再关闭窗口的时候也不会给其发送SIGHUP的信号了。

通过验证果真如此,至此这个消失的进程终于获得了解释,嗯,好在不是咱们代码的问题,值得欣慰,毕竟咱们的代码是要在客户那边运行的,必需要保证好稳定性。

推荐阅读:
当心踩雷,一次Java内存泄漏排查实
跟Kafka学技术系列之时间轮

欢迎关注 PerfMa 社区微信公众号,体验专业JAVA分析工具

相关文章
相关标签/搜索