系统在运行的过程中往往会出现一些意想不到的问题,例如

  1. 响应时长突然升高,甚至应用无响应
  2. 内存使用量突然变大,甚至内存溢出
  3. CPU使用率持续增高
  4. JVM进程消失

面对这些问题,我们往往都需要通过以下的数据来进行分析

  1. 链路追踪,主要用于识别出各接口或方法的耗时情况
  2. Java Core数据,主要用于分析线程的运行情况
  3. Heap Dump转存,主要用于分析JVM内存的使用情况
  4. GC日志,主要分析GC的情况
  5. 数据库执行SQL耗时,主要分析慢SQL


场景1:JVM进程消失

导致JVM进程消失的一般情况有

  1. linux的OOM killer
  2. JVM自身故障
  3. JVM的OOM 


linux的OOM killer

Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。 因此,你发现java进程突然没了,首先要怀疑是不是被linux的OOM killer给干掉了!

系统自身会将系统报错数据存储在以下文件中

/var/log/messages

可以通过以下命令来查询有关杀死进程的信息

egrep -i 'killed process' /var/log/messages

当然,你也可以去内核日志里头查询。有时Linux系统或者系统上运行的java或者其它进程,会发生一些莫名其妙的问题,比如突然挂掉了,比如突然重启等等。在软件上找不到问题所在,此时我们应该怀疑硬件或者内核的问题,此时我们就可以使用 dmesg来查看:

dmesg | grep java

例如以下的输出

[5673702.665338] Out of memory: Kill process 29953 (java) score 431 or sacrifice child
[5673702.665338] Killed process 29953, UID 500, (java) total-vm:9805316kB, anon-rss:2344496kB, file-rss:128kB

通过上面的输出可以看到内核对进程做的操作。


JVM自身故障

当JVM发生致命错误导致崩溃时,会生成一个hs_err_pid_xxx.log这样的文件,该文件包含了导致 JVM crash 的重要信息,我们可以通过分析该文件定位到导致 JVM Crash 的原因,从而修复保证系统稳定。

默认情况下,该文件是生成在工作目录下的,当然也可以通过 JVM 参数指定生成路径:

-XX:ErrorFile=/var/log/hs_err_pid<pid>.log

如何分析该文件,可参考 JVM 致命错误日志(hs_err_pid.log)解读


JVM的OOM 

这种情况往往是因为内存泄漏、代码不合理;或者并发量太大,任务本身会消耗较多内存;或者就是内存配置太小而导致内存溢出。

可以通过添加以下参数来获取内存时的转存文件,然后对文件分析找出问题的代码

#该参数表示当JVM发生OOM时,自动生成DUMP文件。
-XX:+HeapDumpOnOutOfMemoryError

#该参数表示生成DUMP文件的路径,也可以指定文件名称
#如果不指定文件名,默认会在项目根目录下生成一个文件,
#文件名格式为:java_<pid>_<date>_<time>_heapDump.hprof
-XX:HeapDumpPath=./java_heapdump.hprof;

除了通过JVM 参数来生成 dump 文件,还可以通过 jmap来生成任意进程的 dump 文件

# 找到PID
ps -ef | grep java

# jmap生成转存快照
jmap -dump:format=b,file=/tmp/java_heap.dump {PID}


dump文件的分析可参考以下文章

  1. JVM 内存分析工具 MAT 的深度讲解与实践——入门篇
  2. JVM 内存分析工具 MAT 的深度讲解与实践——进阶篇
  3. JVM系列之:MAT工具使用教程
  4. 10 Tips for using the Eclipse Memory Analyzer


场景2:无法提供响应

该场景包含两种情况,一种是新的请求被拒绝了,第二种是响应时长很大。有可能是服务的线程被使用完了,而导致使用完的原因可能有

  1. 线程的死锁
  2. 线程间进行资源竞争时长期等待资源响应
  3. JVM不断的full gc

这时可以抓取Java Core文件进行分析,通过下面的方式可以获取该文件


1、通过Java JDK自带的工具

jstack [-F] [-l] [-m] <pid>

参数说明:

选项

作用

-F

当正常输出的请求不被响应时,强制输出线程堆栈

-m

如果调用到本地方法的话,可以显示C/C++的堆栈

-l

除堆栈外,显示关于锁的附加信息,在发生死锁时可以用jstack -l pid来观察锁持有情况

例如,将进程号为17246的线程转储输出到文件中

jstack -l  17264 > /tmp/threaddump.txt

至于怎么得到进程ID号,可通过linux的ps命令或jdk中的jps工具。

注意:应该在启动进程的所在用户下执行;同时,如果服务器上安装的是JRE而非JDK,那么将不能使用 jstack 工具获取thread dump文件


2、通过Unix/Linux的kill指令

如果服务器上只安装了JRE,那么可以通过kill命令来获取thread dump文件。

kill  -3  <pid>

通过上面指令可以将thread dump信息打印到标准输出中。

同时,通过在Java命令中加上以下参数后可将thread dump输出到文件中。

-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=~/jvm.log
  • 你可以通过-XX:+LogVMOutput开启,或者-XX:-LogVMOutput关闭
  • LogVMOutput必须配合参数-XX:+UnlockDiagnosticVMOptions使用,并且只能加在其后才能生效


场景3:CPU过高

这种情况依旧需要通过Thread Dump文件来分析,其操作过程大致如下

  1. top, 通过top命令找出cpu消耗大的java进程,并得到PID
  2. top -Hp pid,通过该命令查看制定进程下各个线程的cpu使用情况
  3. jstack -l [PID] >/tmp/log.txt , 抓取进程的dump文件
  4. 分析堆栈信息,将线程的ID转换成十六进制后再thread dump文件中查找

可参考文章:Java程序员必备:jstack命令解析



附:其他可参考文章

  1. 为JVM添加故障时的日志参数
  2. Java Thread Dump 日志分析
  3. how-to-read-a-thread-dump
  4. 啃碎并发(四):Java 线程 Dump 分析