Java内存泄漏、性能优化、宕机死锁的N种姿势( 四 )


文章插图
 
宕机被其他进程杀在生产环境发生过进程被清理脚本杀掉 。排查工具有两个:linux自带的auditd和systemtap 。
首先使用auditd , 因为该工具简单易用 , 不用安装 。使用service auditd status检查服务状态 , 如果未启动可用service auditd restart启动 。然后使用命令:auditctl -a exit,always -F arch=b64 -S kill , 监听所有的Kill信号 。如下图所示 , 从type=OBJ_PID行里可以看到:捕捉到的Kill信号杀的进程号opid=40442 , 线程名ocomm=”rocksdb:pst_st” , 注意这里打出的线程名而不是进程名 。
从type=SYSCALL行里可以看到:a1=9表示kill -9;发出kill -9的进程是exe=”/usr/bin/bash” , 进程号是pid=98003 。从这些信息并不能找到相应的进程 , 因为脚本往往运行完就停止 , 生命周期非常短 。

Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
接下来使用systemtap分析 , systemtap需要安装:yum install systemtap systemtap-runtime 。先写systemtap脚本findkiller.stp , 如下所示 , 该systemtap脚本捕捉杀进程sig_pid的KILL信号 , 并使用task_ancestry打印发出KILL信号进程的所有祖先进程 。
probe signal.send{if(sig_name == "SIGKILL" && sig_pid == target()) {printf("%s, %s was sent to %s (pid:%d) by %s (pid:%d) uid :%dn", ctime(gettimeofday_s()), sig_name, pid_name , sig_pid, execname(), pid(), uid());printf("parent of sender: %s(%d)n", pexecname(), ppid());printf("task_ancestry:%sn", task_ancestry(pid2task(pid()), 1));}}然后stap -p4 findkiller.stp生成ko文件:stap_XX.ko , 有的机器需要将ko文件补上签名才能运行 。然后运行:nohup staprun -x 98120 stap_XX.ko >nohup.out 2>&1 & , 此处的98120即为脚本中的target() 。
捕捉结果如下 , 从图里可以看出发出KILL命令的进程是通过crond启动的 , 也就是说定时任务运行了某些脚本杀了进程 。但仍然不知道定时任务启动了哪个脚本杀了进程 。
Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
接下来再用auditd排查 , 使用命令:auditctl -a exit,always -F arch=b64 -S execve捕捉所有的系统调用 , 结果如下 , 最后一行是捕捉到杀进程opid=20286的信号 , 从图中可看出kill信号附近出现的都是/data/tools/clean命令 。
Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
/data/tools/clean里调用了若干脚本 , 在每个脚本里用打出当前脚本名和进程号到crontab.pid里 。并和systemtap抓到的进程号62118对比 , 找到了KILL信号是从kill_non_run_App.sh脚本里发出 。
Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
调用System的exit如果在Java程序里显式调用System.exit结束进程 , 可以用arthas排查 。首先写脚本system_exit.as如下 。
options unsafe truestack java.lang.System exit -n 1运行命令nohup ./as.sh -f system_exit.as 69001 -b > system_exit.out 2>&1 & , 即可监控进程69001调用的所有System.exit 。Java调用的C++发生Crash此处发生的Crash案例和下文Java内Crash产生的原因一样 , 但现象不一样 , 大部分情况下 , 是Crash在C++代码 , 只产生core文件 , 不产生Java内Crash的Crash log;少量情况下Crash在JVM里 , 产生Java内Crash的Crash log 。
如果Java通过JNI调用C++代码 , 在C++里发生Crash , JVM有时不会产生任何信息就退出 , 此时借助操作系统产生的core file分析进程退出原因 , 但操作系统默认关闭该功能 , 如下图所示core file size为0表示关闭该功能 。
Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
因此需要在进程的启动脚本里(只影响当前进程)设置ulimit -c ulimited来设置core file的大小 , 启动进程后 , 打开/proc/{pid}/limits , 查看Max core file size的大小确认是否开启 。
Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
当发生Crash时 , 会生成core.pid文件 , 一般core.pid文件会非常大 , 因为该文件包含了所有虚拟内存大小 , 所以大于物理内存 , 如下图所示core.44729共53GB 。


推荐阅读