JAVA线上程序分析总结

分析线上程序问题

JAVA线上程序分析工具介绍

基础分析工具

java在线编程工具 java程序在线_java在线编程工具

程序内存分析工具

java在线编程工具 java程序在线_后端_02

线程/栈分析工具

java在线编程工具 java程序在线_linux_03

JAVA线上程序问题分析思路

java在线编程工具 java程序在线_jvm_04

部分工具特殊说明

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堆内存的使用情况.

java在线编程工具 java程序在线_后端_05

  • -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线上程序分析总结示例

内存泄露情况分析

  1. 如果程序内存不足或者频繁GC,很有可能存在内存泄露情况,这时候就要借助Java堆Dump查看对象的情况。
  2. 要制作堆Dump可以直接使用jvm自带的jmap命令
  3. 可以先使用jmap -heap命令查看堆的使用情况,看一下各个堆空间的占用情况。
  4. 使用jmap -histo:[live]查看堆内存中的对象的情况。如果有大量对象在持续被引用,并没有被释放掉,那就产生了内存泄露,就要结合代码,把不用的对象释放掉。
  5. 也可以使用 jmap -dump:format=b,file=命令将堆信息保存到一个文件中,再借助jhat命令查看详细内容
  6. 在内存出现泄露、溢出或者其它前提条件下,建议多dump几次内存,把内存文件进行编号归档,便于后续内存整理分析。
  7. 在用cms gc的情况下,执行jmap -heap有些时候会导致进程变T,因此强烈建议别执行这个命令,如果想获取内存目前每个区域的使用状况,可通过jstat -gc或jstat -gccapacity来拿到。

频繁GC与内存泄露

  1. 使用jps查看线程ID
  2. 使用jstat -gc 3331 250 20 查看gc情况,一般比较关注PERM区的情况,查看GC的增长情况。
  3. 使用jstat -gccause:额外输出上次GC原因
  4. 使用jmap -dump:format=b,file=heapDump 3331生成堆转储文件
  5. 使用jhat或者可视化工具(Eclipse Memory Analyzer 、IBM HeapAnalyzer)分析堆情况。
  6. 结合代码解决内存溢出或泄露问题。

死锁问题

  1. 使用jps查看线程ID
  2. 使用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过高的可能

  1. 方法中存在读写文件流的操作,高并发时每个请求产生一个文件流,导致系统CPU急增。
  • 解决方案:
  • 从线程栈日志信息中,找出导致CPU高的线程方法。读写文件流操作移出方法中,避免每次请求都产生一个文件流。
  1. 方法中使用了多线程,未使用连接池或使用了Executors.newCachedThreadPool()创建的接连池,高并发时创建出过多的后台线程。
  • 解决方案:
  • 使用jstack命令统计出线程数量;
  • 找出程序中创建线程代码;
  • 使用Executors.newFixedThreadPool(thread_size)创建固定数量的线程池(线程数固定,无法无收),或者使用new ThreadPoolExecutor(coreSize, maxSize, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())直接构造线程池。
  1. 发现是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的夏天 |