jps

用于显示当前用户下的所有java进程信息:

# jps [options] [hostid] 
# q:仅输出VM标识符, m: 输出main method的参数,l:输出完全的包名, v:输出jvm参数
[root@localhost ~]# jps -l
28729 sun.tools.jps.Jps
23789 cn.ms.test.DemoMain
23651 cn.ms.test.TestMain

jstat

用于监视虚拟机运行时状态信息(类装载、内存、垃圾收集、JIT编译等运行数据):

-gc:垃圾回收统计(大小)

# 每隔2000ms输出<pid>进程的gc情况,一共输出2次
[root@localhost ~]# jstat -gc <pid> 2000 2
# 每隔2s输出<pid>进程的gc情况,每个3条记录就打印隐藏列标题
[root@localhost ~]# jstat -gc -t -h3 <pid> 2s
Timestamp        S0C    S1C    S0U    S1U    ... YGC     YGCT    FGC    FGCT     GCT   
         1021.6 1024.0 1024.0  0.0   1024.0  ...  1    0.012   0      0.000    0.012
         1023.7 1024.0 1024.0  0.0   1024.0  ...  1    0.012   0      0.000    0.012
         1025.7 1024.0 1024.0  0.0   1024.0  ...  1    0.012   0      0.000    0.012
Timestamp        S0C    S1C    S0U    S1U    ... YGC     YGCT    FGC    FGCT     GCT   
         1027.7 1024.0 1024.0  0.0   1024.0  ...  1    0.012   0      0.000    0.012
         1029.7 1024.0 1024.0  0.0   1024.0  ...  1    0.012   0      0.000    0.012
# 结果说明: C即Capacity 总容量,U即Used 已使用的容量
##########################
# S0C:年轻代中第一个survivor(幸存区)的容量 (kb)
# S1C:年轻代中第二个survivor(幸存区)的容量 (kb)
# S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (kb)
# S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (kb)
# EC:年轻代中Eden(伊甸园)的容量 (kb)
# EU:年轻代中Eden(伊甸园)目前已使用空间 (kb)
# OC:Old代的容量 (kb)
# OU:Old代目前已使用空间 (kb)
# PC:Perm(持久代)的容量 (kb)
# PU:Perm(持久代)目前已使用空间 (kb)
# YGC:从应用程序启动到采样时年轻代中gc次数
# YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
# FGC:从应用程序启动到采样时old代(全gc)gc次数
# FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
# GCT:从应用程序启动到采样时gc用的总时间(s)

-gcutil:垃圾回收统计(百分比)

[root@localhost bin]# jstat -gcutil <pid>
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  99.80  16.21  26.18  93.34  90.74      9    0.056     2    0.045    0.102
# 结果说明
##########################
# S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
# S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
# E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
# O:old代已使用的占当前容量百分比
# P:perm代已使用的占当前容量百分比
# YGC:从应用程序启动到采样时年轻代中gc次数
# YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
# FGC:从应用程序启动到采样时old代(全gc)gc次数
# FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
# GCT:从应用程序启动到采样时gc用的总时间(s)

-gccapacity:堆内存统计

[root@localhost ~]# jstat -gccapacity <pid>
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC      PGCMN    PGCMX     PGC       PC     YGC    FGC 
 84480.0 1349632.0 913408.0 54272.0 51200.0 502784.0   168448.0  2699264.0   168448.0   168448.0  21504.0  83968.0  51712.0  51712.0      9     0
# 结果说明
##########################
# NGCMN:年轻代(young)中初始化(最小)的大小 (kb)
# NGCMX:年轻代(young)的最大容量 (kb)
# NGC:年轻代(young)中当前的容量 (kb)
# S0C:年轻代中第一个survivor(幸存区)的容量 (kb)
# S1C:年轻代中第二个survivor(幸存区)的容量 (kb)
# EC:年轻代中Eden(伊甸园)的容量 (kb)
# OGCMN:old代中初始化(最小)的大小 (kb)
# OGCMX:old代的最大容量 (kb)
# OGC:old代当前新生成的容量 (kb)
# OC:Old代的容量 (kb)
# PGCMN:perm代中初始化(最小)的大小 (kb)
# PGCMX:perm代的最大容量 (kb)
# PGC:perm代当前新生成的容量 (kb)
# PC:Perm(持久代)的容量 (kb)
# YGC:从应用程序启动到采样时年轻代中gc次数
# GCT:从应用程序启动到采样时gc用的总时间(s)

-gccause:垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因

[root@localhost ~]# jstat -gccause <pid>
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC       
  0.00  79.23  39.37  39.92  99.74      9    0.198     0    0.000    0.198 Allocation Failure   No GC
# 结果说明
##########################
# LGCC:最近垃圾回收的原因
# GCC:当前垃圾回收的原因

jstack

jstack(Java Stack Trace)主要用于打印线程的堆栈信息,是JDK自带的很强大的线程分析工具,可以帮助我们排查程序运行时的线程状态、死锁状态等。

# dump出进程<pid>的线程堆栈快照至/data/1.log文件中
jstack -l <pid> >/data/1.log

# 参数说明:
# -F:如果正常执行jstack命令没有响应(比如进程hung住了),可以加上此参数强制执行thread dump
# -m:除了打印Java的方法调用栈之外,还会输出native方法的栈帧
# -l:打印与锁有关的附加信息。使用此参数会导致JVM停止时间变长,在生产环境需慎用

jstack dump文件中值得关注的线程状态有:

  • 死锁(Deadlock) —— 重点关注
  • 执行中(Runnable)
  • 等待资源(Waiting on condition) —— 重点关注
    • 等待某个资源或条件发生来唤醒自己。具体需结合jstacktrace来分析,如线程正在sleep,网络读写繁忙而等待
    • 如果大量线程在“waiting on condition”,并且在等待网络资源,可能是网络瓶颈的征兆
  • 等待获取监视器(Waiting on monitor entry) —— 重点关注
    • 如果大量线程在“waiting for monitor entry”,可能是一个全局锁阻塞住了大量线程
  • 暂停(Suspended)
  • 对象等待中(Object.wait() 或 TIMED_WAITING)
  • 阻塞(Blocked) —— 重点关注
  • 停止(Parked)

注意:如果某个相同的call stack经常出现, 我们有80%的以上的理由确定这个代码存在性能问题(读网络的部分除外)。

场景一:分析BLOCKED问题

"RMI TCP Connection(267865)-172.16.5.25" daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000]
   java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:201)
- waiting to lock <0x00000000acf4d0c0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)
  • 线程状态是 Blocked,阻塞状态。说明线程等待资源超时
  • “ waiting to lock <0x00000000acf4d0c0>”指,线程在等待给这个 0x00000000acf4d0c0 地址上锁(英文可描述为:trying to obtain 0x00000000acf4d0c0 lock)
  • 在 dump 日志里查找字符串 0x00000000acf4d0c0,发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁(如locked < 0x00000000acf4d0c0 >),就可以顺藤摸瓜了
  • “waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区,从而进入了下图1中的“Entry Set”队列,但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待
  • 第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址。

场景二:分析CPU过高问题

命令找出最高占用的进程(Shift+P)

2.查看高负载进程下的高负载线程(top -Hp <PID>或ps -mp <PID> -o THREAD,tid,time)

3.找出最高占用的线程并记录thread_id,把线程号进行换算成16进制编号(printf "%X\n" thread_id)

4.(可选)执行查看高负载的线程名称(jstack 16143 | grep 3fb6)

5.导出进程的堆栈日志,找到3fb6 这个线程号(jstack 16143 >/home/16143.log)

6.根据找到的堆栈信息关联到代码进行定位分析即可