前言
事实企业级 Java 开发中,有时候咱们会碰到上面这些问题:
1.OutOfMemoryError,内存不足
2. 内存泄露
3. 线程死锁
4. 锁争用(Lock Contention)
5.Java 过程耗费 CPU 过高
……
这些问题在日常开发中可能被很多人漠视(比方有的人遇到下面的问题只是重启服务器或者调大内存,而不会深究问题本源),但可能了解并解决这些问题是 Java 程序员进阶的必备要求。本文将对一些罕用的 JVM 性能调优监控工具进行介绍,心愿能起抛砖引玉之用。本文参考了网上很多材料,难以一一列举,在此对这些材料的作者表示感谢!对于 JVM 性能调优相干的材料,请参考文末。
A、jps(Java Virtual Machine Process Status Tool)
jps 次要用来输入 JVM 中运行的过程状态信息。语法格局如下:
jps [options] [hostid]
复制代码
如果不指定 hostid 就默认为以后主机或服务器。
命令行参数选项阐明如下:
-q 不输入类名、Jar 名和传入 main 办法的参数
-m 输入传入 main 办法的参数
-l 输入 main 类或 Jar 的全限名
-v 输入传入 JVM 的参数
复制代码
比方上面:
root@ubuntu:/# jps -m -l
2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml
29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat
3149 org.apache.catalina.startup.Bootstrap start
30972 sun.tools.jps.Jps -m -l
8247 org.apache.catalina.startup.Bootstrap start
25687 com.sun.tools.hat.Main -port 9999 dump.dat
21711 mrf-center.jar
复制代码
B、jstack
jstack 次要用来查看某个 Java 过程内的线程堆栈信息。语法格局如下:
jstack [option] pid jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-ip 命令行参数选项阐明如下:
-l long listings,会打印出额定的锁信息,在产生死锁时能够用 jstack -l pid 来察看锁持有状况 -m mixed mode,不仅会输入 Java 堆栈信息,还会输入 C /C++ 堆栈信息(比方 Native 办法)jstack 能够定位到线程堆栈,依据堆栈信息咱们能够定位到具体代码,所以它在 JVM 性能调优中应用得十分多。上面咱们来一个实例找出某个 Java 过程中最消耗 CPU 的 Java 线程并定位堆栈信息,用到的命令有 ps、top、printf、jstack、grep。
第一步先找出 Java 过程 ID,我部署在服务器上的 Java 利用名称为 mrf-center:
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar 失去过程 ID 为 21711,第二步找出该过程内最消耗 CPU 的线程,能够应用 ps -Lfp pid 或者 ps -mp pid -o THREAD, tid, time 或者 top -Hp pid,我这里用第三个,输入如下:
TIME 列就是各个 Java 线程消耗的 CPU 工夫,CPU 工夫最长的是线程 ID 为 21742 的线程,用
printf "%xn" 21742
复制代码
失去 21742 的十六进制值为 54ee,上面会用到。
OK,下一步终于轮到 jstack 上场了,它用来输入过程 21711 的堆栈信息,而后依据线程 ID 的十六进制值 grep,如下:
root@ubuntu:/# jstack 21711 | grep 54ee
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
复制代码
能够看到 CPU 耗费在 PollIntervalRetrySchedulerThread 这个类的 Object.wait(),我找了下我的代码,定位到上面的代码:
// Idle wait
getLog().info("Thread [" + getName() + "] is idle waiting...");
schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
long now = System.currentTimeMillis();
long waitTime = now + getIdleWaitTime();
long timeUntilContinue = waitTime - now;
synchronized(sigLock) {
try {if(!halted.get()) {sigLock.wait(timeUntilContinue);
}
}
catch (InterruptedException ignore) {}}
复制代码
它是轮询工作的闲暇期待代码,下面的 sigLock.wait(timeUntilContinue) 就对应了后面的 Object.wait()。
C、jmap(Memory Map)和 jhat(Java Heap Analysis Tool)
jmap 用来查看堆内存应用情况,个别联合 jhat 应用。
jmap 语法格局如下:
jmap [option] pid
jmap [option] executable core
jmap [option] [server-id@]remote-hostname-or-ip
复制代码
如果运行在 64 位 JVM 上,可能须要指定 -J-d64 命令选项参数。
jmap -permstat pid
复制代码
打印过程的类加载器和类加载器加载的长久代对象信息,输入:类加载器名称、对象是否存活(不牢靠)、对象地址、父类加载器、已加载的类大小等信息,如下图:
应用 jmap -heap pid 查看过程堆内存应用状况,包含应用的 GC 算法、堆配置参数和各代中堆内存应用状况。比方上面的例子:
root@ubuntu:/# jmap -heap 21711
Attaching to process ID 21711, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.10-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2067791872 (1972.0MB)
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 6422528 (6.125MB)
used = 5445552 (5.1932830810546875MB)
free = 976976 (0.9317169189453125MB)
84.78829520089286% used
From Space:
capacity = 131072 (0.125MB)
used = 98304 (0.09375MB)
free = 32768 (0.03125MB)
75.0% used
To Space:
capacity = 131072 (0.125MB)
used = 0 (0.0MB)
free = 131072 (0.125MB)
0.0% used
PS Old Generation
capacity = 35258368 (33.625MB)
used = 4119544 (3.9287033081054688MB)
free = 31138824 (29.69629669189453MB)
11.683876009235595% used
PS Perm Generation
capacity = 52428800 (50.0MB)
used = 26075168 (24.867218017578125MB)
free = 26353632 (25.132781982421875MB)
49.73443603515625% used
....
复制代码
应用 jmap -histo[:live] pid 查看堆内存中的对象数目、大小统计直方图,如果带上 live 则只统计活对象,如下:
root@ubuntu:/# jmap -histo:live 21711 | more
num #instances #bytes class name
----------------------------------------------
1: 38445 5597736 <constMethodKlass>
2: 38445 5237288 <methodKlass>
3: 3500 3749504 <constantPoolKlass>
4: 60858 3242600 <symbolKlass>
5: 3500 2715264 <instanceKlassKlass>
6: 2796 2131424 <constantPoolCacheKlass>
7: 5543 1317400 [I
8: 13714 1010768 [C
9: 4752 1003344 [B
10: 1225 639656 <methodDataKlass>
11: 14194 454208 java.lang.String
12: 3809 396136 java.lang.Class
13: 4979 311952 [S
14: 5598 287064 [[I
15: 3028 266464 java.lang.reflect.Method
16: 280 163520 <objArrayKlassKlass>
17: 4355 139360 java.util.HashMap$Entry
18: 1869 138568 [Ljava.util.HashMap$Entry;
19: 2443 97720 java.util.LinkedHashMap$Entry
20: 2072 82880 java.lang.ref.SoftReference
21: 1807 71528 [Ljava.lang.Object;
22: 2206 70592 java.lang.ref.WeakReference
23: 934 52304 java.util.LinkedHashMap
24: 871 48776 java.beans.MethodDescriptor
25: 1442 46144 java.util.concurrent.ConcurrentHashMap$HashEntry
26: 804 38592 java.util.HashMap
27: 948 37920 java.util.concurrent.ConcurrentHashMap$Segment
28: 1621 35696 [Ljava.lang.Class;
29: 1313 34880 [Ljava.lang.String;
30: 1396 33504 java.util.LinkedList$Entry
31: 462 33264 java.lang.reflect.Field
32: 1024 32768 java.util.Hashtable$Entry
33: 948 31440 [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
复制代码
class name 是对象类型,阐明如下:
B byte
C char
D double
F float
I int
J long
Z boolean
[数组,如 [I 示意 int[]
[L+ 类名 其余对象
复制代码
还有一个很罕用的状况是:用 jmap 把过程内存应用状况 dump 到文件中,再用 jhat 剖析查看。jmap 进行 dump 命令格局如下:
jmap -dump:format=b,file=dumpFileName pid
复制代码
我一样地对下面过程 ID 为 21711 进行 Dump:
root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711
Dumping heap to /tmp/dump.dat ...
Heap dump file created
复制代码
dump 进去的文件能够用 MAT、VisualVM 等工具查看,这里用 jhat 查看:
root@ubuntu:/# jhat -port 9998 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Tue Jan 28 17:46:14 CST 2014
Snapshot read, resolving...
Resolving 132207 objects...
Chasing references, expect 26 dots..........................
Eliminating duplicate references..........................
Snapshot resolved.
Started HTTP server on port 9998
Server is ready.
复制代码
留神如果 Dump 文件太大,可能须要加上 -J-Xmx512m 这种参数指定最大堆内存,即 jhat -J-Xmx512m -port 9998 /tmp/dump.dat。而后就能够在浏览器中输出主机地址:9998 查看了:
下面红线框出来的局部大家能够本人去摸索下,最初一项反对 OQL(对象查询语言)。
D、jstat(JVM 统计监测工具)
语法格局如下:
jstat [generalOption | outputOptions vmid [interval[s|ms] [count]] ]
复制代码
vmid 是 Java 虚拟机 ID,在 Linux/Unix 零碎上个别就是过程 ID。interval 是采样工夫距离。count 是采样数目。比方上面输入的是 GC 信息,采样工夫距离为 250ms,采样数为 4:
root@ubuntu:/# jstat -gc 21711 250 4
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
192.0 192.0 64.0 0.0 6144.0 1854.9 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
192.0 192.0 64.0 0.0 6144.0 2109.7 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
复制代码
要明确下面各列的意义,先看 JVM 堆内存布局:
能够看出:
堆内存 = 年老代 + 年轻代 + 永恒代
年老代 = Eden 区 + 两个 Survivor 区(From 和 To)复制代码
当初来解释各列含意:
S0C、S1C、S0U、S1U:Survivor 0/ 1 区容量(Capacity)和使用量(Used)EC、EU:Eden 区容量和使用量
OC、OU:年轻代容量和使用量
PC、PU:永恒代容量和使用量
YGC、YGT:年老代 GC 次数和 GC 耗时
FGC、FGCT:Full GC 次数和 Full GC 耗时
GCT:GC 总耗时
复制代码
E、hprof(Heap/CPU Profiling Tool)
hprof 可能展示 CPU 使用率,统计堆内存应用状况。
语法格局如下:
java -agentlib:hprof[=options] ToBeProfiledClass
java -Xrunprof[:options] ToBeProfiledClass
javac -J-agentlib:hprof[=options] ToBeProfiledClass
复制代码
残缺的命令选项如下:
Option Name and Value Description Default
--------------------- ----------- -------
heap=dump|sites|all heap profiling all
cpu=samples|times|old CPU usage off
monitor=y|n monitor contention n
format=a|b text(txt) or binary output a
file=<file> write data to file java.hprof[.txt]
net=<host>:<port> send data over a socket off
depth=<size> stack trace depth 4
interval=<ms> sample interval in ms 10
cutoff=<value> output cutoff point 0.0001
lineno=y|n line number in traces? y
thread=y|n thread in traces? n
doe=y|n dump on exit? y
msa=y|n Solaris micro state accounting n
force=y|n force output to <file> y
verbose=y|n print messages about dumps y
复制代码
来几个官网指南上的实例。
CPU Usage Sampling Profiling(cpu=samples) 的例子:
java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello
复制代码
下面每隔 20 毫秒采样 CPU 耗费信息,堆栈深度为 3,生成的 profile 文件名称是 java.hprof.txt,在当前目录。
CPU Usage Times Profiling(cpu=times) 的例子,它绝对于 CPU Usage Sampling Profile 可能取得更加细粒度的 CPU 耗费信息,可能细到每个办法调用的开始和完结,它的实现应用了字节码注入技术(BCI):
javac -J-agentlib:hprof=cpu=times Hello.java
复制代码
Heap Allocation Profiling(heap=sites) 的例子:
javac -J-agentlib:hprof=heap=sites Hello.java
复制代码
Heap Dump(heap=dump) 的例子,它比下面的 Heap Allocation Profiling 能生成更具体的 Heap Dump 信息:
javac -J-agentlib:hprof=heap=dump Hello.java
复制代码
尽管在 JVM 启动参数中退出 -Xrunprof:heap=sites 参数能够生成 CPU/Heap Profile 文件,但对 JVM 性能影响十分大,不倡议在线上服务器环境应用。
参考:《2020 最新 Java 根底精讲视频教程和学习路线!》
链接:https://juejin.cn/post/691668…