JAVA线上程序分析总结
分析线上程序问题
JAVA线上程序分析工具介绍
基础分析工具
程序内存分析工具
线程/栈分析工具
JAVA线上程序问题分析思路
部分工具特殊说明
jstat查看堆信息,gc等实时情况
jstat [Options] vmid [interval] [count]
参数说明:
- Options,选项,我们一般使用 -gcutil 查看gc情况
- vmid,VM的进程号,即当前运行的java进程号
- interval,间隔时间,单位为秒或者毫秒
- count,打印次数,如果缺省则打印无数次
- Option选项说明
- l class (查看类加载情况的统计)
用法:jstat –class [pid]
说明:Loaded:加载了的类的数量
Bytes:加载了的类的大小,单为 Kb
Unloaded:卸载了的类的数量
Bytes:卸载了的类的大小,单为 Kb
Time:花在类的加载及卸载的时间
- l compiler (JIT,查看Hotspot中即时编译器编译情况的统计)
用法:jstat -compiler [pid]
说明:Loaded:Compiled:编译任务执行的次数
Failed:编译任务执行失败的次数
Invalid:编译任务非法执行的次数
Time:执行编译花费的时间
FailedType:最后一次编译失败的编译类型
FailedMethod:最后一次编译失败的类名及方法名
- l gc (显示gc的信息,查看 gc 的次数,及时间)
用法:jstat -gc [pid]
说明:单位为KB或秒
- l gccapacity (显示,VM 内存中三代(young,old,perm)对象的使用和占用大小。新生代、老生代及持久代的存储容量情况)
用法:jstat -gccapacity [pid]
说明:单位为KB或秒
- l gccause (最近一次GC统计和原因)
- l gcnew (年轻代对象的信息,新生代垃圾收集的情况)
- l gcnewcapacity (年轻代对象的信息及其占用量,新生代的存储容量情况)
- l gcold (old 代对象的信息,老生代及持久代发生 GC 的情况)
- l gcoldcapacity (old 代对象的信息及其占用量,老生代的存储容量情况)
- l gcpermcapacity (永久区大小[jdk<=1.7])
- l gcutil (统计 gc 信息,新生代、老生代及持代垃圾收集的情况)
用法:jstat -gcutil [pid]
说明:单位为KB或秒
- l printcompilation (HotSpot编译统计)
- l gcmetacapacity (元数据空间统计[jdk>=1.8]perm 对象的信息及其占用量)
- | printcompilation(当前 VM 执行的信息)
用法:jstat -printcompilation [pid]
说明:Compiled:编译任务执行的次数
Size:方法的字节码所占的字节数
Type:编译类型
Method:指定确定被编译方法的类名及方法名,类名中使名“/”而不是“.”做为命名分隔符,方法名是被指定的类中的方法,这两个字段的格式是由
HotSpot中的“-XX:+PrintComplation”选项确定的。
- 结果参数说明
显示内容说明如下(部分结果是通过其他其他参数显示的,暂不说明):
S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC:年轻代中Eden(伊甸园)的容量 (字节)
EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC:Old代的容量 (字节)
OU:Old代目前已使用空间 (字节)
PC:Perm(持久代)的容量 (字节)
PU:Perm(持久代)目前已使用空间 (字节)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
NGCMN:年轻代(young)中初始化(最小)的大小 (字节)
NGCMX:年轻代(young)的最大容量 (字节)
NGC:年轻代(young)中当前的容量 (字节)
OGCMN:old代中初始化(最小)的大小 (字节)
OGCMX:old代的最大容量 (字节)
OGC:old代当前新生成的容量 (字节)
PGCMN:perm代中初始化(最小)的大小 (字节)
PGCMX:perm代的最大容量 (字节)
PGC:perm代当前新生成的容量 (字节)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
P:perm代已使用的占当前容量百分比
M:元数据区使用比例
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
TT: 持有次数限制
MTT : 最大持有次数限制
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
jmap查看堆内存信息,jvm,demp等信息
命令格式:
jmap [option] (to connect to running process) 连接到正在运行的进程
jmap [option] <executable (to connect to a core file) 连接到核心文件
jmap [option] [server_id@](to connect to remote debug server) 连接到远程调试服务参数说明(options之后的参数):
pid: 目标进程的PID,进程编号,可以采用ps -ef | grep java 查看java进程的PID;
executable: 产生core dump的java可执行程序;
core: 将被打印信息的core dump文件;
remote-hostname-or-IP: 远程debug服务的主机名或ip;
server-id: 唯一id,假如一台主机上多个远程debug服务;基本参数(替换options的参数)
- -dump:[live,]format=b,file= 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
- -finalizerinfo 打印正等候回收的对象的信息
- -heap 打印heap的概要信息,GC使用的算法,heap(堆)的配置及JVM堆内存的使用情况.
- -histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
采用jmap -histo pid>a.log日志将其保存,在一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象。
jmap -dump:format=b,file=outfile 3024可以将3024进程的内存heap输出出来到outfile文件里,再配合MAT(内存分析工具)
- -permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
- -F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
- -h | -help 打印辅助信息
- -J 传递参数给jmap启动的jvm.
使用示例:
查看线上堆快照信息并分析
jmap -dump:format=b,file=/opt/heap.hprof 8303 (导出堆快照文件)
使用jdk中bin中的工具jvisualvm.exe来查看堆快照信息
双击jvisualvm.exe打开–>文件–>装入–>选择Dump文件类型–>找到堆快照文件–>打开查看实例数或对象内存占用大小
参数说明:[num #instances(实例) #bytes(字节大小) class name(类名)]
[root@iZwz9dylctz1awz4epuhuyZ ~]# jmap -histo 13020 |grep com.pkk|sort -k 2 -g -r|less (按照实例多少进行排序展示)
[root@iZwz9dylctz1awz4epuhuyZ ~]# jmap -histo 13020 |grep com.pkk|sort -k 3 -g -r|less(按照对象内存占用大小进行排序展示,单位为bytes)
JAVA线上程序分析总结示例
内存泄露情况分析
- 如果程序内存不足或者频繁GC,很有可能存在内存泄露情况,这时候就要借助Java堆Dump查看对象的情况。
- 要制作堆Dump可以直接使用jvm自带的jmap命令
- 可以先使用jmap -heap命令查看堆的使用情况,看一下各个堆空间的占用情况。
- 使用jmap -histo:[live]查看堆内存中的对象的情况。如果有大量对象在持续被引用,并没有被释放掉,那就产生了内存泄露,就要结合代码,把不用的对象释放掉。
- 也可以使用 jmap -dump:format=b,file=命令将堆信息保存到一个文件中,再借助jhat命令查看详细内容
- 在内存出现泄露、溢出或者其它前提条件下,建议多dump几次内存,把内存文件进行编号归档,便于后续内存整理分析。
- 在用cms gc的情况下,执行jmap -heap有些时候会导致进程变T,因此强烈建议别执行这个命令,如果想获取内存目前每个区域的使用状况,可通过jstat -gc或jstat -gccapacity来拿到。
频繁GC与内存泄露
- 使用jps查看线程ID
- 使用jstat -gc 3331 250 20 查看gc情况,一般比较关注PERM区的情况,查看GC的增长情况。
- 使用jstat -gccause:额外输出上次GC原因
- 使用jmap -dump:format=b,file=heapDump 3331生成堆转储文件
- 使用jhat或者可视化工具(Eclipse Memory Analyzer 、IBM HeapAnalyzer)分析堆情况。
- 结合代码解决内存溢出或泄露问题。
死锁问题
- 使用jps查看线程ID
- 使用jstack 3331:查看线程情况
CPU占用过高排查
首先你需要知道那个项目有问题,或者通过top/atop知道那个进程占用较高的cpu
- 观察进程中线程的cpu占用情况
方法1. 可用通过top -Hp [指定PID]
方法2. 也可以通过top -p [指定PID] 然后在按shift + h 开启线程显示
- 进行线程堆栈分析
1. 记录刚刚显示的线程中占用cpu较高的线程id(TID)
2. 使用printf "%x\n" [指定TID(刚刚占用较高的线程id)] 将线程对应PID转为 16进制数
3. jstack PID | grep -A 30 "nid=0x + TID16" :查看该线程的堆栈信息
- 小讲解grep -A 30:表示 除匹配到的该行外额外显示(After)改行之后的30行
- 小讲解grep -B 30:表示 除匹配到的该行外额外显示(Before)改行之前的30行
- 小讲解grep 30:表示 除匹配到的该行外额外显示(Before)改行之前和之后的各30行
4. 通过线程的堆栈信息,定位到CPU占用过高的代码,分析其原因
- 常可以结合jstat来分析JVM中的内存信息;(如jstat -gcutil PID 1000 10 、jstat -gc PID 1000 10、jstat -gccause PID 1000 10)
- jmap -heap PID来查看整个JVM内存状态 ;(要注意的是在使用CMS GC情况下,jmap -heap的执行有可能会导致JAVA进程挂起)
- jmap -histo PID 查看JVM堆中对象详细占用情况;
- jmap -dump:format=b,file=文件名 [pid]导出整个JVM中内存信息。
几种导致CPU过高的可能
- 方法中存在读写文件流的操作,高并发时每个请求产生一个文件流,导致系统CPU急增。
- 解决方案:
- 从线程栈日志信息中,找出导致CPU高的线程方法。读写文件流操作移出方法中,避免每次请求都产生一个文件流。
- 方法中使用了多线程,未使用连接池或使用了Executors.newCachedThreadPool()创建的接连池,高并发时创建出过多的后台线程。
- 解决方案:
- 使用jstack命令统计出线程数量;
- 找出程序中创建线程代码;
- 使用Executors.newFixedThreadPool(thread_size)创建固定数量的线程池(线程数固定,无法无收),或者使用new ThreadPoolExecutor(coreSize, maxSize, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())直接构造线程池。
- 发现是gc线程导致的CPU问题比较费时
- 解决方案:
- 查看一下gc策略是否合理;
- 用命令jmap -histo [PID] 分析是哪个类占用内存比较多,分析出可能存在内存泄露的地方;
- jvm内存调优,可使用Jconsole、visalvm、probe等工具查看java虚拟机中方法区、堆区(新生代、幸存代、老年代)、线程栈的内存分配,根据实际情况进行优化。
常用命令总结
- jmap -heap pid 【查看进程的jvm堆的情况,新生,老年代的情况】
- jmap -histo[:live] pid |sort -k 3 -g -r |less > size.txt 【打印[存活]的对象按照大小进行排序(-g数字排序,-r反转倒序)然后输入到size.txt中】
- jmap -histo[:live] pid |sort -k 2 -g -r |less > count.txt 【打印[存活]的对象按照数量进行排序(-g数字排序,-r反转倒序)然后输入到count.txt中】
- jmap -dump:live,file=/opt/dump.hprof pid 【dump一个java存活的对象的栈信息到文件中,可以用jhat线上7000端口分析,也可以下载下来用JvisualVM进行分析】
- jstat -gcutil pid 1s 10 [查看进程的最近的GC汇总情况,一秒输出一次,输出10次]
- jstat -gc 1s 10 [查看进程的最近的GC情况,一秒输出一次,输出10次]
- 扩展
> #instance 是对象的实例个数 #bytes 是总占用的字节数 class name 对应的就是 Class 文件里的 class 的标识 B 代表 byte C 代表 char D 代表 double F 代表 float I 代表 int J 代表 long Z 代表 boolean 前边有 [ 代表数组, [I 就相当于 int[] 对象用 [L+ 类名表示
Reference
- 线程的运行状态解析 | 旁观者 |
- 线上使用内存泄露的分析 | savilors |
- Java后端服务明显变慢诊断思路 | hunger_wang |
- MySQL 加锁处理分析 | 何登成的技术博客 | http://hedengcheng.com/?p=771
- jstat参数解析 天朝P民甲 |
- 常见问题分析 | WangCw的夏天 |