Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Shell,信号量以及java进程的退出

1,159 views

Published on

shell, signal, and how jvm handle signal

Published in: Technology
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Shell,信号量以及java进程的退出

  1. 1. Shell,信号量与Java进程的退出 错刀 2017-3-11 http://hongjiang.info
  2. 2. 问题 Java 怎么实现一个常驻的服务?
  3. 3. 第一个例子 • public class Daemon { • public static void main(String[] args) { • new Thread() { • public void run() { • while (true) { • try { • // doSomething(); 该线程周期性的做一些事 情 • Thread.sleep(2000); • } catch (Exception e) { • } • } • } • }.start(); • } • }
  4. 4. 现在把程序部署到服务器上 • ssh 到某台 linux 环境 • $ java Daemon &
  5. 5. 然后你合上笔记本,准备愉快的回家了
  6. 6. 在你刚走到电梯的时候,收到了报警
  7. 7. Who killed java ?
  8. 8. 问题 • 谁创建的Java进程? – 它的父进程是谁? 它会继承父进程什么? • 谁杀死了Java进程? – 通过什么方式?
  9. 9. Unix下进程的特性
  10. 10. 进程皆有父
  11. 11. 进程皆有名
  12. 12. 进程皆可衍生
  13. 13. 进程皆有资源限制
  14. 14. 进程可能会死
  15. 15. 进程皆有退出码
  16. 16. 进程皆有可获得信号
  17. 17. 信号是通过什么命令发送?
  18. 18. Kill 注意在大多数shell 下它同时也是一个内置命令 $ echo $0 && type -a kill -bash kill is a shell builtin kill is /usr/bin/kill ➜ echo $0 && type -a kill zsh kill is a shell builtin kill is /bin/kill % echo $0 && type -a kill csh kill is a shell builtin kill is /bin/kill
  19. 19. Kill
  20. 20. SIGHUP, SIGQUIT, SIGINT, SIGTERM, SIGSTOP, SIGKILL 字面上都有停止的意思,它们的区别是什么?
  21. 21. 常见Signal以及JVM处理方式
  22. 22. SIGHUP
  23. 23. JVM如何处理 SIGHUP ? $ kill -1 `pgrep java`
  24. 24. 守护进程对SIGHUP的处理 $ kill –s HUP pidof_nginx “This signal [SIGHUP] is commonly used to notify daemon processes (Chapter 13) to reread their configuration files. The reason SIGHUP is chosen for this is because a daemon should not have a controlling terminal and would normally never receive this signal.” <<Advanced Programming in the UNIX Environment>>
  25. 25. SIGINT (ctrl-c)
  26. 26. JVM如何处理 SIGINT? $ kill -2 `pgrep java`
  27. 27. SIGQUIT (ctrl-)
  28. 28. JVM如何处理SIGQUIT? $ kill -3 `pgrep java` 跟 jstack 类似,额外多了一点信息
  29. 29. SIGQUIT 并不会让JVM产生coredump, 如何才能产生?
  30. 30. JVM如何产生coredump? 第一种方式,发送 SIGILL 或 SIGBUG 信号,jvm crash前会产生coredump # 启动java前需要去掉限制 $ ulimit -c unlimited $ java Daemon # 发送 SIGILL 或 SIGBUS 信号给目标 java 进程 $ kill –s ILL `pgrep java` 等同于 kill -4 或 $ kill –s BUS `pgrep java` 等同于 kill -7 (Mac下等同于 kill -10) # 产生的coredunp 文件可在 hs_err_pidxxx.log里查找 $ grep 'Core dump' hs_err_pid37418.log Core dump written. Default location: /cores/core or core.37418 # 如果目标进程没有unlimited 可能会无法生成 coredump 文件
  31. 31. JVM如何产生coredump? 第二种方式,通过gdb来产生coredump, jvm不会crash # 使用 gdb attach 到目标进程 $ sudo gdb --pid=19936 ... (gdb) gcore /tmp/jvm.core warning: target file /proc/19936/cmdline contained unexpected null characters Saved corefile /tmp/jvm.core (gdb) detach (gdb) quit #coredump文件可以作为源,再进行heapdump,比直接用 jmap对目标进程 heapdump更快 $ jmap -dump:format=b,file=jvm.hprof /usr/bin/java /tmp/jvm.core
  32. 32. SIGSTOP & SIGCONT
  33. 33. SIGSTOP scala> var a=0; scala> while(true) { Thread.sleep(2000); println(a); a=a+1 } 0 1 2 $ ps -ostat -p `pidof java` STAT S+ $ kill -s stop `pidof java` 此时jvm进程被暂停住,进程状态也变为: T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态 $ ps -ostat -p `pidof java` STAT T+
  34. 34. SIGCONT $ kill -s cont `pidof java` $ ps -ostat -p `pidof java` STAT S+ java进程恢复运行状态,继续循环输出
  35. 35. SIGUSR1 & SIGUSR2
  36. 36. SIGUSR1 $ kill –s USR1 pidof_nginx Logrotate $ kill –s USR1 pidof_java Jvm 退出 $ kill –s USR2 pidof_java 被识别为 SIGSEGV, Jvm Crash (Coredump)
  37. 37. SIGPIPE
  38. 38. SIGPIPE • Java Socket connection reset 异常与 SIGPIPE 的关系?
  39. 39. Broken Pipe
  40. 40. Broken pipe
  41. 41. FIN_WAIT_2
  42. 42. Connection Reset
  43. 43. SIGTERM
  44. 44. JVM如何处理SIGTERM $ kill `pgrep java`
  45. 45. 临终遗言或垂死挣扎: ShutdownHook
  46. 46. ShutdownHook scala> Runtime.getRuntime.addShutdownHook( new Thread(new Runnable { def run () { println("shutdown"); Thread.sleep(10000); println("END"); } }))
  47. 47. 自行了断 • OOM Fail-fast -XX:OnOutOfMemoryError="kill -9 %p" • Akka jvm-exit-on-fatal-error = on
  48. 48. SIGKILL
  49. 49. SIGKILL • ShutdownHook 将不会被执行 • 什么情况下 kill -9 也无法结束一个进程?
  50. 50. SIGKILL • 内核什么情况下会发给Java进程SIGKILL? • oom-Killer • cpu time limit
  51. 51. SIGKILL $ ulimit -t 10 $ scala scala> while(true){} # 在另一个终端监听 java收到的signal $ sudo strace -e trace=signal -p 27708 Process 27708 attached +++ killed by SIGKILL +++
  52. 52. 改变 Signal 的处理行为: 自定义 Signal Handler
  53. 53. JVM Signal Handler scala> import sun.misc._ scala> Signal.handle(new Signal("INT"), | new SignalHandler(){ | def handle(sig:Signal){ println("down") }} | ) $ kill -s INT `pidof java` scala> down // 被捕获,进程不会退出
  54. 54. JVM Signal Handler 注意,被OS或JVM已注册了的 Signal 不能被修改,比如 SIGQUIT、 SIGUSR1 等 scala> Signal.handle( | new Signal("QUIT"), | new SignalHandler(){ | def handle(sig:Signal){ println("down") } | } | ) java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGQUIT at sun.misc.Signal.handle(Signal.java:166) ... 38 elided
  55. 55. 忽略系统的signal
  56. 56. JVM -Xrs参数(reduce signal) $ scala -J-Xrs $ kill -3 `pidof java` 这个时候确实不会产生 thread dump, 并且java进程会退出。
  57. 57. signal dispatcher线程 • http://hllvm.group.iteye.com/group/topic/39 570 • http://ifeve.com/jvm-attach/
  58. 58. Shell:一个特殊的进程
  59. 59. Shell的几种模式 • login shell • non-login shell • interactive shell • non-interactive shell
  60. 60. 非交互式shell • 以脚本方式运行 • 运行时不会显式加载用户profile/rc
  61. 61. 交互式shell
  62. 62. 交互式shell • 加载用户 profile/rc – 自行google bash 启动时加载哪些文件 – 作为 login-shell 和 non-login-shell 时有什么不同
  63. 63. 前台任务与后台任务 • Foreground process – 运行时阻塞shell • Background process – 运行时不阻塞shell
  64. 64. 后台任务 #!/bin/bash sleep 5 & # 模拟并行任务1 pid1=$! sleep 5 & # 模拟并行任务2 pid2=$! sleep 5 & # 模拟并行任务3 pid3=$! wait $pid1 $pid2 $pid3 && echo "all done"
  65. 65. 作业控制 作业控制是指有选择的停止(暂停)并在后来 继续(恢复)执行某个进程的能力。 交互式shell默认开启作业控制 非交互式shell默认关闭作业控制
  66. 66. 交互式shell下的作业控制
  67. 67. 交互式shell下的作业控制 $ vim # 在 vim 下执行 ctrl-z # vim 收到 SIGTSTP 信号,进程暂停并被移到后台 $ jobs [1] + suspended vim $ fg %1 # vim 收到 SIGCONT 信号,进程恢复并切换回前台执行 Sigtstp 与 Sigstop 类似,通常 sigtstp 是来自键盘 ( ctrl-z )
  68. 68. 交互式shell下的作业控制 关闭作业控制 bash-4.4$ set +m bash-4.4$ sleep 10 & [1] 94679 bash-4.4$ fg %1 bash: fg: no job control
  69. 69. 进程组 $ echo $$ 400 $ find / 2 > /dev/null | wc –l & [1] 659 $ sort < longlist | uniq -c
  70. 70. PID=400 PPID=399 PGID=400 SID=400 bash PID=658 PPID=400 PGID=658 SID=400 find PID=659 PPID=400 PGID=658 SID=400 wc PID=660 PPID=400 PGID=660 SID=400 sort PID=661 PPID=400 PGID=660 SID=400 uniq 进程组 400 进程组 658 进程组 660 后台进程组 前台进程组 进程组首进程
  71. 71. session PID=400 PPID=399 PGID=400 SID=400 bash PID=658 PPID=400 PGID=658 SID=400 find PID=659 PPID=400 PGID=658 SID=400 wc PID=660 PPID=400 PGID=660 SID=400 sort PID=661 PPID=400 PGID=660 SID=400 uniq 进程组 400 进程组 658 进程组 660 会话400 会话 首进程
  72. 72. Session • 新进程会继承父进程的会话ID • 同一会话中的所有进程共享单个控制终端 • 会话中只有一个前台进程组,可有多个后 台进程组
  73. 73. Terminal
  74. 74. Terminal • 计算机考古 一台大型主机往往需要支持许多用户同时使用,每个用户所 使用操作的设备,就叫做Termial——终端,终端使用通信电 缆与电脑主机连接,甚至可以通过电信网络(电话、电报线 路等等)连接另一个城市的电脑。
  75. 75. 图片来源:https://www.zhihu.com/question/21711307 在上图中的那种带显示屏的视频终端出现之前,TTY是最流行的终 端设备
  76. 76. tty: 电传打字机 TeleTYpewriter 是早期的终端(物理设备),它们用于向计算机发送数 据,并将计算机的返回结果打印出来。显示器出现后,终端不再将结 果打印出来,而是显示在显示器上。但是tty的名字还是保留了下来。
  77. 77. 终端与控制进程 控制终端的连接建立起来之后(即打开终端), 会话首进程会成为该终端的控制进程。
  78. 78. PID=400 PPID=399 PGID=400 SID=400 bash PID=658 PPID=400 PGID=658 SID=400 find PID=659 PPID=400 PGID=658 SID=400 wc PID=660 PPID=400 PGID=660 SID=400 sort PID=661 PPID=400 PGID=660 SID=400 uniq 进程组 400 进程组 658 进程组 660 会话 400 共享同一个控制终端 (tty) 控制进程
  79. 79. 终端与控制进程 当一个控制进程失去其终端连接之后,内核 会向其发送一个SIGHUP信号。(还会发送一个 SIGCONT信号以确保当该进程之前被一个信号 停止时重新开始该进程),一般发生在两个场 景下: 1) 终端驱动器(调制解调器)连接断开 2) 工作站上的终端窗口被关闭
  80. 80. 当shell收到sighup后,它对子进程如何处理?
  81. 81. 1) 第一个终端下(bash)执行 sleep 4400 $ sleep 4400 2) 在第二个终端下监听 sleep 4400 进程的 singal $ ps xf | grep -C2 sleep 24327 pts/2 Ss 0:00 _ -bash 24361 pts/2 S+ 0:00 _ sleep 4400 $ sudo strace -e trace=signal -p 24361 3) 用鼠标关闭第一个终端窗口 SIGHUP的链式反应
  82. 82. SIGHUP的链式反应 bash的子进程也收到 SIGHUP $ sudo strace -e trace=signal -p 24361 Process 24361 attached --- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL, si_value={int=1004881496, ptr=0x7f123be54658}} --- +++ killed by SIGHUP +++
  83. 83. Shell 正常退出对后台进程组的影响 • 取决于具体的shell实现,bash下有个参数设置, 默认在正常退出时不会对后台子进程发送 sighup • $ shopt huponexit huponexit off 在bash下通过 exit 或 ctrl-d 退出,其后台进程不 会收到 sighup,但不排除受其它signal影响
  84. 84. 回顾最初的例子: $ java Daemon & 它在交互式shell下启了一个后台任务,终端 断开后(网络断开) shell 收到了 kernel 的 sighup 执行退出,并将 sighup 发送给了它的 子进程,java 进程收到 sighup 后也退出。
  85. 85. 如何解决? • 让后台服务进程忽略sighup是关键
  86. 86. 摆脱 SIGHUP 1)nohup $ nohup java Daemon & 2) disown $ java Daemon & $ disown 3) setsid 设置为一个新的 session $ setsid java Daemon &
  87. 87. 摆脱 SIGHUP 另一种惯用的技巧: subshell
  88. 88. 孤儿进程
  89. 89. 孤儿进程 • 一个父进程退出,而它的一个或多个子进 程还在运行,那么那些子进程将成为孤儿 进程。孤儿进程将被init (或systemd) 进程 (进程号为1)所收养,并由init进程对它们完 成状态收集工作。
  90. 90. 摆脱 SIGHUP: subshell $ ( java Daemon &) 在当前shell下额外起来一个新的subshell作为java的父 进程,等同于在当前shell下执行下面的脚本: $ cat start.sh #!/bin/sh java Daemon & $ ./start.sh
  91. 91. 一个现实中的案例
  92. 92. #!/bin/bash cd /data/program/tomcat7/bin/ ./catalina.sh start tail -f /data/program/tomcat7/logs/catalina.out
  93. 93. 问题
  94. 94. 将 catalina.sh start 简化: #!/bin/sh eval '"/pathofjdk/bin/java"’ 'params' org.apache.catalina.startup.Bootstrap start '&’ 脚本运行后 java 进程会挂到 pid为1的进程下
  95. 95. 进程树
  96. 96. • 用 ctrl-c 终止当前test.sh进程时,系统events 进程向 java 和 tail 两个进程发送了SIGINT 信 号 • SIGINT [ 0 11 ] -> [ 0 20629 tail ] • SIGINT [ 0 11 ] -> [ 0 20628 java ] • SIGINT [ 0 11 ] -> [ 0 20615 test.sh ]
  97. 97. • 关闭ssh终端窗口时,sshd向下游进程发送SIGHUP, 为何java进程也会收到? • SIGHUP [ 0 11681 sshd: hongjiang [priv] ] -> [ 57316 11700 bash ] • SIGHUP [ 57316 11700 -bash ] -> [ 57316 11700 bash ] • SIGHUP [ 57316 11700 ] -> [ 0 13299 tail ] • SIGHUP [ 57316 11700 ] -> [ 0 13298 java ] • SIGHUP [ 57316 11700 ] -> [ 0 13285 test.sh ]
  98. 98. • 交互模式与非交互模式对作业控制(job control)默认方式不同 • 交互模式下shell不会对后台进程处理SIGINT 信号设置为忽略,而非交互模式下会设置 为忽略
  99. 99. • 在非交互模式下,shell对java进程设置了 SIGINT,SIGQUIT信号设置了忽略,但并没 有对SIGHUP信号设为忽略。
  100. 100. 再看一下当时的进程层级: |-sshd--sshd--sshd--bash--test.sh--tail sshd把SIGHUP传递给bash进程后,bash会把 SIGHUP传递给它的子进程,并且对于其子进程 test.sh,bash还会对test.sh的进程组里的成员都 传播一遍SIGHUP。因为java后台进程从父进程 catalina.sh(又是从其父进程test.sh)继承的pgid, 所以java进程仍属于test.sh进程组里的成员,收 到SIGHUP后退出。
  101. 101. Subshell 没有退出导致Java进程没有跟控制进 程(bash)之间有效的脱离关系 这个例子Java进程由2层subshell 来启动, test.sh以及 catalina.sh 尽管 catalina.sh 启动后 退出,但 test.sh 这层 subshell 被 tail 进程给阻 塞着,控制进程可以将 SIGHUP 发给 test.sh 进 程组
  102. 102. • 一个更复杂的关于作业控制的例子: http://hongjiang.info/job-control-and- foreground-process-group/ • 书籍参考:《Linux系统编程手册》
  103. 103. Q&A

×