一、 如何查看进程启动时间,持续时长
ps -eo pid,lstart,etime,cmd |grep -v grep |grep 进程名称
pid 进程号
lstart 启动时间
etime 持续时长
cmd 启动命令
二、CPU 100% 排查
1. 确定出问题的进程。
top -c 显示运行中的进程列表信息,shift + p 使列表按 cpu 使用率排序显示
发现 PID 为 28555 的 Java 进程占用 CPU 高达 200%,出现故障。
通过 ps aux | grep PID 命令,可以进一步确定是 tomcat 进程出现了问题。
2. 定位到具体线程或者代码
显示线程列表找到了耗时最高的线程(tid):
找到了耗时最高的线程 28802,占用CPU时间快两个小时了!
ps -mp pid -o THREAD,tid,time
或者
使用命令: top -Hp {pid} ,同样 shift + p 可按 cpu 使用率对线程列表进行排序
3. 其次将需要的线程ID(tid)转换为 16 进制格式:
如 tid=28802 的 16 进制为:7082
printf "%x\n" tid
4. 最后打印线程的堆栈信息:
jstack pid | grep tid -A 30
如:jstack 28555 | grep 7082 -A 30
jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。
-A 10 表示查找到所在行的后10行
根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
线程快照格式都是统一的,下面以一个线程快照简单说明下:
"main" #1 prio=5 os_prio=0 tid=0x0000000002792800 nid=0x3e1c runnable [0x00000000025cf000]
三、内存过高排查方法
1. 回顾
首先我们先回顾下Java进程的内存分配,方便我们下面排查思路的阐述。
以我们线上使用的JDK1.8版本为例。JVM内存分配网上有许多总结,我就不再进行二次创作。
JVM内存区域的划分为两块:堆区和非堆区。
- 堆区:就是我们熟知的新生代老年代。
- 非堆区:非堆区如图中所示,有元数据区和直接内存。 这里需要额外注意的是:永久代(JDK8 的原生区)存放 JVM 运行时使用的类,永久代的对象在full GC时进行垃圾收集。
2. JVM内存参数设置
- 堆内存设置 堆内存(总的)由 -Xms 和 -Xmx 分别设置最小和最大堆内存
New Generation 由 -Xmn 设置,-XX:SurvivorRatio=m 设置 Eden和 两个Survivor区的大小比值;-XX:NewRatio=n 设置 New Generation 和 Old Generation 的大小比值。
每个线程的堆栈大小由 ·-Xss· 设置,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
- 非堆内存设置 非堆内存由 -XX:PermSize=n 和 -XX:MaxPermSize=n 分别设置最小和最大非堆内存大小
排查思路
1. 找到内存占有率最高的进程号
使用命令: top -c 显示运行中的进程列表信息, shift + m 按内存使用率进行排序
2. 利用 jmap 生成堆转储快照
PS命令可以查到具体进程的CPU占用情况,但是不能查到一个进程下具体线程的内存占用情况。
ps -mp 9004 -o THREAD,tid,time,rss,size,%mem
只好寻求其他方法了,幸好Java提供了一个很好的内存监控工具:jmap 命令
jmap 命令命令有下面几种常用的用法:
jmap [pid]
jmap -histo:live [pid] > a.log
jmap -dump:live,format=b,file=xxx.xxx [pid]
用得最多是后面两个。其中:
jmap -histo:live [pid]
可以查看当前Java进程创建的活跃对象数目和占用内存大小。
jmap -dump:live,format=b,file=xxx.xxx [pid]
则可以将当前Java进程的内存占用情况导出来,方便用专门的内存分析工具(例如:MAT:Memory Analyzer Tool)来分析。这个命令对于分析是否有内存泄漏很有帮助。
3. 利用 MAT 分析 dump 文件
注意:
默认情况下,mat 最大内存是 1024m ,而我们的 dump 文件往往大于 1024m,所以我们需要调整,在 mat 的 home 目录下找到 MemoryAnalyzer.ini ,将 -Xmx1024m 修改成大于 dump 大小的空间, 我把它改成了 -Xmx6G
四、总结
1. JVM 常用命令
jps:列出正在运行的虚拟机进程
jinfo:实时查看和调整虚拟机各项参数
jstat:监视虚拟机各种运行状态信息,可以显示虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
jmap:生成堆转储快照,也可以查询 finalize 执行队列、Java 堆和永久代的详细信息
jstack:生成虚拟机当前时刻的线程快照
jhat:虚拟机堆转储快照分析工具,与 jmap 搭配使用,分析 jmap 生成的堆转储快照,与 MAT 的作用类似
jinfo
首先,我们使用 jinfo pid 查看当前 jvm 的堆相关参数:
可见,最大堆容量为:4G。
jstat
接下来,我们使用命令 jstat -gcutil pid 1s 5 查看5秒内当前堆占用情况:
如上,新生代已经满了(占用97.33%),老年代也已经满了(占用100%),同时 FGC 高达 967 次!FGC 的数量太大了,正常来说 FGC 应该占整个 GC(YGC+FGC)的 1%-5% 才正常。
jmap
除了 jstat 命令外,我们也可以使用 jmap -heap pid 查看下当前JVM堆情况
2. 排查步骤
1) 先找到对应的进程: PID
2) 生成线程快照 stack (或堆转储快照: hprof )
3) 分析快照(或堆转储快照),定位问题
3. 内存泄露、内存溢出和 CPU 100% 关系
参考
JVM 常见线上问题 → CPU 100%、内存泄露 问题排查
内存过高排查方法 MAT:一次线上内存泄漏排查
一次完整的JVM堆外内存泄漏故障排查记录 Java内存泄漏分析系列之三:jstat命令的使用及VM Thread分析