线上的jvm故障基本可以分为两大类:
CPU____占用过高。
内存问题,通常可以理解为gc的问题,因为java的内存用gc进行管理。
命令行工具
jps等工具都是对tools.jar类的包装,使用起来方便简单.在下边的故障排查中会用到我们这里提到的工具,大家平时应该熟记于心.
jps
jps -l pid #输出主类的全名,如果进程执行的是jar包,输出jar包路径
jps -v pid #输出虚拟机进程启动时JVM参数
#我想监控gc,每250ms查询一次,一共查询20次,进程号为123
jstat -gc 123 250 20
jmap pid
jmap -histo:live pid > a.log #当前Java进程创建的活跃对象数目和占用内存大小
jmap -dump:live, format=b,file=xxx.xxx pid #当前Java进程的内存占用情况导出来
图形工具
1. jconsole: JVM各状态查看工具
CPU负载比较高的时候,我们需要先找到那个java进程,然后根据(进程ID)找到的那个”问题线程”,根据线程的堆栈信息找到代码,最后进行代码排查。
1. top命令定位到cpu消耗最高的进程,并记住进程pid
通过 top -Hp pid 找到问题线程,记住线程 tid
2. 通过jstack -l tid >jstack.log 将线程堆栈信息dump到指定文件中
线程tid 是十进制的,堆栈中的线程id是16进制,使用 printf “%x\n” tid 转换。
3. 通过转化的16进制数字从堆栈信息中找到对应的线程堆栈.
如果有gc线程,可以推断内存泄露导致频发的gc,可以通过 jstat -gcutil pid 1s查看
[root@localhost ~]# jstat -gcutil 27534 1s
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 100.00 99.34 91.25 82.89 52648 463.968 1439 3701.045 3473.868
0.00 0.00 89.65 99.34 91.25 82.89 52649 463.975 1440 3711.702 3476.880
0.00 0.00 100.00 99.34 91.25 82.89 52649 463.975 1440 3711.702 3476.880
0.00 0.00 100.00 99.34 91.25 82.89 52649 463.975 1443 3714.241 3479.891
0.00 0.00 100.00 99.34 91.25 82.89 52649 463.975 1443 3714.241 3479.891
0.00 0.00 100.00 99.34 91.25 82.89 52649 463.975 1443 3714.241 3479.891
0.00 0.00 100.00 99.34 91.25 82.89 52649 463.975 1444 3719.604 3483.889
可以看到每隔几秒就会full gc,而且Eden和Old都是99%,就是说每次full gc都没有回收到多少内存,所以一直在反复的跑
[root@localhost ~]# java -XX:+PrintFlagsFinal -version | grep manageable
intx CMSAbortablePrecleanWaitMillis = 100 {manageable}
intx CMSTriggerInterval = -1 {manageable}
intx CMSWaitDuration = 2000 {manageable}
bool HeapDumpAfterFullGC = false {manageable}
bool HeapDumpBeforeFullGC = false {manageable}
bool HeapDumpOnOutOfMemoryError = false {manageable}
ccstr HeapDumpPath = {manageable}
uintx MaxHeapFreeRatio = 100 {manageable}
uintx MinHeapFreeRatio = 0 {manageable}
bool PrintClassHistogram = false {manageable}
bool PrintClassHistogramAfterFullGC = false {manageable}
bool PrintClassHistogramBeforeFullGC = false {manageable}
bool PrintConcurrentLocks = false {manageable}
bool PrintGC = false {manageable}
bool PrintGCDateStamps = false {manageable}
bool PrintGCDetails = false {manageable}
bool PrintGCID = false {manageable}
bool PrintGCTimeStamps = false {manageable}
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
用jinfo打开以下选项,把full gc前后的虚拟机内存dump下来
jinfo -flag +PrintGC pid
jinfo -flag +PrintGCDetails pid
jinfo -flag HeapDumpPath=/home/rain/heapdump pid
jinfo -flag +HeapDumpBeforeFullGC pid
jinfo -flag +HeapDumpAfterFullGC pid
PrintGC和PrintGCDetails把gc日志输出到了nohup.out,查看nohup文件,可以看到full gc前后各dump了一次虚拟机内存,然后赶紧用jinfo关掉gc选项,选项前+号表示打开,-号表示关闭.
jinfo -flag -HeapDumpBeforeFullGC pid
jinfo -flag -HeapDumpAfterFullGC pid
在HeapDumpPath下找到dump下来的hprof文件,下载下来用Jprofile,jvisualvm 等工具都可以分析
这里简单的说下,在java虚拟机中,内存分为: 新生代(Eden), 老年代(Old),永久代.
新生代存放朝生夕死的对象。
老年代存放从新生代迁移过来的周期较久的对象.
永久代是非堆内存的组成部分.主要存放加载类的class类级对象,比如说class本身,method,field等等。
在进行内存问题处理的时候,我们经常会碰到以下两种异常:
java.lang.OutOfMemoryError: PermGen space
如果出现这个异常,一般都是程序启动需要加载大量的第三方jar包,需要调整perm的内存设置
java.lang.OutOfMemoryError: Java heap space
如果出现这个异常,一般是由于虚拟机设置堆内存过小或者代码创建了大量的大对象,并且长时间不能被回收.
通常gc管理内存,一种是内存溢出,一种是内存没有溢出,gc处于亚健康情况
内存溢出的情况可以通过加上 -XX:+HeapDumpOnOutOfMemoryError 参数,该参数作用是:在程序内存溢出时输出 dump 文件
内存不溢出的情况比较复杂,一般gc会将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor.当回收时,将Eden和Survivor中还存活着的对象一次性拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间.HotSpot虚拟机默认Eden和Survivor的大小比例是8:1
YGC会经过两个过程,一个是扫描,一个是复制,扫描比较快,复制相对慢一些,如果每次都有大量的对象复制,STW(stop the world)时间会延长,另外一种情况是gc和系统的swap同时进行,也会延长STW时间
FGC触发原因有以下几个方面:
1.Old区内存不足
2.元数据区内存不足
3.cms promotion failed
4.concurrent mode failure.
5.jvm基于悲观策略认为ygc后old区无法放下晋升对象
6.jmap触发或者系统触发System.gc()
一般gc健康的情况下,YGC 5秒一次左右,每次不超过50毫秒,FGC 最好没有,CMS GC 一天一次左右gc超过5秒,说明系统内存过大,如果YGC频率过高,说明Eden区过小,可以增加Eden去。
内存问题的排查思路和cpu类似,在进行cpu分析的时候也顺带说了下内存:
通过top命令定位内存消耗最高的进程,并记住进程pid
jmap -histo:live pid查看当前进程创建的活跃对象的数目和占用内存的大小,从而定位代码。
对于一般的问题,通过这几个方面的思考,大致可以锁定问题所在,或是缩小问题可能发生的范围。例如对某些特定类型的内存泄漏来说,到这一步已经可以分析出是什么类型导致内存泄漏。