前提概要:
JDK本身提供了很多方便的JVM性能调优监控工具,有jps、jstack、jmap、jhat、jstat、hprof,VisualVM和jConsole等工具,VisualVM和jConsole作为其中两个可视化工具,当然是最直观最易懂的,不过在linux环境一些无图形界面的操作系统上,其他的小工具就会显得格外的实在。
一般企业级Java开发中,最常遇到的问题:内存不足、内存泄露、线程死锁、Java进程消耗CPU过高。 对于大多数开发人员基本都归结与程序的缺陷,但是从哪里入手找到这些问题,似乎并不是一个普通开发所擅长解决的事情。下面就来告诉你如何通过以上小工具来分析和定位。
一、 jps(Java Virtual Machine Process Status Tool)
语法格式:
jps [options] [hostid]
[options]:
-q 不输出类名、Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 输出main类或Jar的全限名
-v 输出传入JVM的参数
[hostid]
如果不指定hostid就默认为当前主机或服务器。
示例:
root@dr-574cb5cf8f-82jdb:/# jps -v
6 jar -Xms6G -Xmx6G -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:ParallelGCThreads=4 -XX:InitiatingHeapOccupancyPercent=40 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/nas/logs/dr/heap_trace.txt -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpBeforeFullGC -XX:+HeapDumpAfterFullGC -XX:HeapDumpPath=/nas/logs/dr/heap_dump.hprof -Dapp.name=data-retrieval
104 Jps -Dapplication.home=/usr/lib/jvm/java-8-openjdk-amd64 -Xms8m
二、 jstack
jstack主要用来查看某个Java进程内的线程堆栈信息。
语法格式:
jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip
[option]:
-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
-m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法)
-F 当使用jstack <pid>无响应时,强制输出线程堆栈。
jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在JVM性能调优中使用得非常多。
示例:
找出某个Java进程中最耗费CPU的Java线程并定位堆栈信息:
1、先找出Java进程ID
root@dr-6fbc7fd678-t4vdg:/# jps -ml
6 data-retrieval-runner-1.0.jar
24473 sun.tools.jps.Jps -ml
root@dr-6fbc7fd678-t4vdg:/#
这里的java进程PID为6
2.找出该进程内最耗费CPU的线程:
top -Hp 6
top - 17:08:37 up 47 days, 3:21, 0 users, load average: 0.16, 0.12, 0.29
KiB Mem: 32779828 total, 25450424 used, 7329404 free, 95160 buffers
KiB Swap: 0 total, 0 used, 0 free. 3124264 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
81 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 10:12.86 java
84 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 9:42.66 java
79 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 9:28.21 java
83 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 8:49.11 java
78 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 8:03.91 java
82 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 8:03.83 java
87 root 20 0 13.980g 7.683g 11460 S 0.0 24.6 7:40.32 java
top命令后,按快捷T按照时间排序。这里最耗性能的线程为81号线程。
3. 将十进制的线程号转化为十六进制
root@dr-6fbc7fd678-t4vdg:/# printf "%x\n" 81
51
使用shell函数转化,你可以口算。
然后执行 jstack -l pid 这里的pid 是进程的pid
精确查找:jstack -l pid |grep 0x51
"pool-9-thread-4" #69 prio=5 os_prio=0 tid=0x00007efe71a28000 nid=0x51 waiting on condition [0x00007efec7cfb000]
这里"pool-9-thread-4" 这个线程是比较耗性能。
说明:本案例整个应用是维护一个线程池,然后不停的消费消息队列中的任务。我这个应用的负载比较小,代码也不存在任何问题,这里处理相对来说是我系统中比较耗CPU的地方。并不代表这里就有问题。
三、 jmap(Memory Map)和 jhat(Java Heap Analysis Tool):
jmap导出堆内存,然后使用jhat来进行分析。jmap用来查看堆内存使用状况,一般结合jhat使用。
语法格式:
jmap [option] <pid> (连接正在执行的进程)
jmap [option] <executable <core> (连接一个core文件)
jmap [option] [server_id@]<remote server IP or hostname> (链接远程服务器)
如果运行在64位JVM上,由于linux操作系统的不同,可能需要指定-J-d64命令选项参数。
[option]:
-heap 打印java heap摘要
-histo[:live] 打印堆中的java对象统计信息
-clstats 打印类加载器统计信息
-finalizerinfo 打印在f-queue中等待执行finalizer方法的对象
-dump:<dump-options> 生成java堆的dump文件
dump-options:
live 只转储存活的对象,如果没有指定则转储所有对象
format=b 二进制格式
file=<file> 转储文件到 <file>
-F 强制选项
示例:
1. 把java堆中的存活对象信息转储到dump.hprof文件
root@dr-6fbc7fd678-t4vdg:/# jmap -dump:live,format=b,file=/home/dump.hprof 6
Dumping heap to /home/dump.hprof ...
Heap dump file created
2. 输出堆的详细信息
root@dr-6fbc7fd678-t4vdg:/# jmap -heap 6
Attaching to process ID 6, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.111-b14
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 8589934592 (8192.0MB)
NewSize = 2863136768 (2730.5MB)
MaxNewSize = 2863136768 (2730.5MB)
OldSize = 5726797824 (5461.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
整个是堆栈的一些设置。具体意思这里就不在解释。不明白可以参照博客的其他文章,有专门讲解的
3. 输出存活对象统计信息
root@dr-6fbc7fd678-t4vdg:/# jmap -histo:live 6 | more
num #instances #bytes class name
----------------------------------------------
1: 40607943 974590632 java.lang.String
2: 6044125 946298048 [D
3: 9113 492465352 [I
4: 8208512 450366536 [C
5: 18460625 443055000 com.brucexx.dr.txt.dat.DatNeuron
6: 4844604 232540992 weka.classifiers.trees.RandomTree$Tree
7: 425202 110350808 [Ljava.lang.Object;
8: 2421642 58119408 [Lweka.classifiers.trees.RandomTree$Tree;
9: 130000 40951536 [B
10: 840946 26910272 java.util.HashMap$Node
11: 555168 24505416 [Ljava.util.Hashtable$Entry;
12: 1985 23992072 [Z
13: 276884 22150720 weka.core.Attribute
14: 561242 17959744 java.util.Hashtable$Entry
15: 445573 17822920 java.util.TreeMap$Entry
16: 276884 15505504 weka.core.ProtectedProperties
17: 883758 14140128 java.lang.Integer
18: 278008 13344384 java.util.Hashtable
19: 418177 10036248 java.util.ArrayList
20: 20216 8851848 [Ljava.util.HashMap$Node;
我这里太多了,这里只展示20行,看效果。
class name是对象类型,说明如下:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
四、jhat
jhat是用来分析jmap生成dump文件的命令,jhat内置了应用服务器,可以通过网页查看dump文件分析结果,jhat一般是用在离线分析上。
语法格式:
jhat [option] [dumpfile]
option参数解释:
-stack false: 关闭对象分配调用堆栈的跟踪
-refs false: 关闭对象引用的跟踪
-port <port>: HTTP服务器端口,默认是7000
-debug <int>: debug级别
0: 无debug输出
1: Debug hprof file parsing
2: Debug hprof file parsing, no server
-version 分析报告版本
示例:
jhat dump.hprof
五、jstat
jstat命令是使用频率比较高的命令,主要用来查看JVM运行时的状态信息,包括内存状态、垃圾回收等。.
语法格式:
jstat [option] LVMID [interval] [count]
其中LVMID是进程id,interval是打印间隔时间(毫秒),count是打印次数(默认一直打印)
option参数解释:
-class class loader的行为统计
-compiler HotSpt JIT编译器行为统计
-gc 垃圾回收堆的行为统计
-gccapacity 各个垃圾回收代容量(young,old,perm)和他们相应的空间统计
-gcutil 垃圾回收统计概述
-gccause 垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因
-gcnew 新生代行为统计
-gcnewcapacity 新生代与其相应的内存空间的统计
-gcold 年老代和永生代行为统计
-gcoldcapacity 年老代行为统计
-gcpermcapacity 永生代行为统计
-printcompilation HotSpot编译方法统计
示例:
pid为6,每隔1000毫秒打印一次,打印4次
jstat -gc 6 1000 4
root@dr-6fbc7fd678-t4vdg:/usr/lib/jvm/java-8-openjdk-amd64/bin# jstat -gc 6 1000 4
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
12800.0 12288.0 6790.6 0.0 2770944.0 314589.3 5592576.0 3938355.6 102976.0 98690.0 10624.0 9660.2 2614 58.562 6 18.197 76.759
12800.0 12288.0 6790.6 0.0 2770944.0 315367.7 5592576.0 3938355.6 102976.0 98690.0 10624.0 9660.2 2614 58.562 6 18.197 76.759
12800.0 12288.0 6790.6 0.0 2770944.0 316227.1 5592576.0 3938355.6 102976.0 98690.0 10624.0 9660.2 2614 58.562 6 18.197 76.759
12800.0 12288.0 6790.6 0.0 2770944.0 317143.2 5592576.0 3938355.6 102976.0 98690.0 10624.0 9660.2 2614 58.562 6 18.197 76.759
字段解释:
S0C survivor0大小
S1C survivor1大小
S0U survivor0已使用大小
S1U survivor1已使用大小
EC Eden区大小
EU Eden区已使用大小
OC 老年代大小
OU 老年代已使用大小
MC 方法区大小
MU 方法区已使用大小
CCSC 压缩类空间大小
CCSU 压缩类空间已使用大小
YGC 年轻代垃圾回收次数
YGCT 年轻代垃圾回收消耗时间
FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间
GCT 垃圾回收消耗总时间
jstat -gcutil 6 1000 4
root@dr-6fbc7fd678-t4vdg:/usr/lib/jvm/java-8-openjdk-amd64/bin# jstat -gcutil 6 1000 4
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
53.05 0.00 12.21 70.42 95.84 90.93 2614 58.562 6 18.197 76.759
53.05 0.00 12.24 70.42 95.84 90.93 2614 58.562 6 18.197 76.759
53.05 0.00 12.28 70.42 95.84 90.93 2614 58.562 6 18.197 76.759
53.05 0.00 12.30 70.42 95.84 90.93 2614 58.562 6 18.197 76.759
字段解释:
S0 survivor0使用百分比
S1 survivor1使用百分比
E Eden区使用百分比
O 老年代使用百分比
M 元数据区使用百分比
CCS 压缩使用百分比
YGC 年轻代垃圾回收次数
YGCT 年轻代垃圾回收消耗时间
FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间
GCT 垃圾回收消耗总时间
分析一下:
Minor GC=58.562/2614 = 20ms
Full GC=18.197/6 = 3ms
次要GC一共执行2614次,耗时:58.562,差不多每个20ms,属于回收比较快的,没啥问题;
Full GC一共执行了6次,说明执行的次数并不是很多,内存比较充足。 但是full GC的时间为3s,执行时间比较长,说明内存设置比较大的原因。
优化参数显示:
内存比较充足,如果你服务器资源比较少,可以减少内存大小;
3天执行了6次full gc,full GC的频率比较低。每次full gc比较长,说明一次full gc的时候,处理的内容比较多。如果服务器资源比较少,可以适当的减少内存分配; 另外回收策略可以进一步优化。
五、hprof(Heap/CPU Profiling Tool):
hprof能够展现CPU使用率,统计堆内存使用情况。
HPROF: 一个Heap/CPU Profiling工具:J2SE中提供了一个简单的命令行工具来对java程序的cpu和heap进行 profiling,叫做HPROF。HPROF实际上是JVM中的一个native的库,它会在JVM启动的时候通过命令行参数来动态加载,并成为 JVM进程的一部分。若要在java进程启动的时候使用HPROF,用户可以通过各种命令行参数类型来使用HPROF对java进程的heap或者 (和)cpu进行profiling的功能。HPROF产生的profiling数据可以是二进制的,也可以是文本格式的。这些日志可以用来跟踪和分析 java进程的性能问题和瓶颈,解决内存使用上不优的地方或者程序实现上的不优之处。二进制格式的日志还可以被JVM中的HAT工具来进行浏览和分析,用 以观察java进程的heap中各种类型和数据的情况。在J2SE 5.0以后的版本中,HPROF已经被并入到一个叫做Java Virtual Machine Tool Interface(JVM TI)中。