JVM调优工具

JVM参数汇总查看命令

java -XX:+PrintFlagsInitial:打印出所有参数选项的默认值
java -XX:+PrintFlagsFinal:打印出所有参数选项在运行程序时生效的值

jps

jps的作用是显示当前系统的java进程情况及进程id

1
2
3
jps
25712 Jps
25900 Launcher
1
2
3
4
5
6
# 输出传递给main方法的参数
jps -m
# 输出应用程序main class的完整package名或者应用程序的jar文件完整路径名
jps -l
# 输出传递给JVM的参数
jps -v

jmap

用来查看内存信息实例个数以及占用内存大小

1
2
# 查看内存占用排名前20的对象
jmap -histo PID | head -20

存活实例统计:jmap -histo 23960 > ./log.txt

jmap -histo pid

  • num:序号
  • instances:实例数量
  • bytes:占用空间大小
  • class name:类名称

堆内存统计:jmap -heap 26500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Attaching to process ID 26500, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11

using thread-local object allocation.
Parallel GC with 6 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4273995776 (4076.0MB)
NewSize = 89128960 (85.0MB)
MaxNewSize = 1424490496 (1358.5MB)
OldSize = 179306496 (171.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 96993280 (92.5MB)
used = 14883328 (14.19384765625MB)
free = 82109952 (78.30615234375MB)
15.34470016891892% used
From Space:
capacity = 11010048 (10.5MB)
used = 10977376 (10.468841552734375MB)
free = 32672 (0.031158447265625MB)
99.70325288318452% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 111149056 (106.0MB)
used = 13376792 (12.757102966308594MB)
free = 97772264 (93.2428970336914MB)
12.035002798404333% used

15634 interned Strings occupying 2076984 bytes.

堆内存dump:jmap -dump:format=b,file=test.hprof 26500

1
2
Dumping heap to C:\Users\90627\Desktop\test.hprof ...
Heap dump file created

也可以通过-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=./test 参数设置内存溢出自动导出dump文件,内存很大的时候,可能会导不出来,可以将导出的dump文件导入到jvisualvm 进行查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\jvm.dump
*/
public class OOMTest {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int i = 0;
int j = 0;
while (true) {
list.add(new User(i++, UUID.randomUUID().toString()));
new User(j--, UUID.randomUUID().toString());
}
}
}

jstack

用jstack加进程id查找死锁。如下是死锁示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class DeadLockTest {
private static Object lock1 = new Object();
private static Object lock2 = new Object();

public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " end");
}
}
}, "thread1").start();

new Thread(() -> {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " end");
}
}
}, "thread2").start();
}
}

执行后具体日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
jstack 13728
2021-08-19 21:39:46
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):

"thread2" #12 prio=5 os_prio=0 tid=0x000000001f35d000 nid=0x665c waiting for monitor entry [0x000000001fb9f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.eleven.icode.jvm.DeadLockTest.lambda$main$1(DeadLockTest.java:34)
- waiting to lock <0x000000076b3a5530> (a java.lang.Object)
- locked <0x000000076b3a5540> (a java.lang.Object)
at com.eleven.icode.jvm.DeadLockTest$$Lambda$2/2121055098.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"thread1" #11 prio=5 os_prio=0 tid=0x000000001f33f800 nid=0x2cf0 waiting for monitor entry [0x000000001fa9f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.eleven.icode.jvm.DeadLockTest.lambda$main$0(DeadLockTest.java:20)
- waiting to lock <0x000000076b3a5540> (a java.lang.Object)
- locked <0x000000076b3a5530> (a java.lang.Object)
at com.eleven.icode.jvm.DeadLockTest$$Lambda$1/1225358173.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

Found one Java-level deadlock:
=============================
"thread2":
waiting to lock monitor 0x000000001cb8ec78 (object 0x000000076b3a5530, a java.lang.Object),
which is held by "thread1"
"thread1":
waiting to lock monitor 0x000000001cb91508 (object 0x000000076b3a5540, a java.lang.Object),
which is held by "thread2"

Java stack information for the threads listed above:
===================================================
"thread2":
at com.eleven.icode.jvm.DeadLockTest.lambda$main$1(DeadLockTest.java:34)
- waiting to lock <0x000000076b3a5530> (a java.lang.Object)
- locked <0x000000076b3a5540> (a java.lang.Object)
at com.eleven.icode.jvm.DeadLockTest$$Lambda$2/2121055098.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"thread1":
at com.eleven.icode.jvm.DeadLockTest.lambda$main$0(DeadLockTest.java:20)
- waiting to lock <0x000000076b3a5540> (a java.lang.Object)
- locked <0x000000076b3a5530> (a java.lang.Object)
at com.eleven.icode.jvm.DeadLockTest$$Lambda$1/1225358173.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

还可以用jvisualvm自动检测死锁

检测线程死锁

启动JAR的JMX端口配置
1
java -Dcom.sun.management.jmxremote.port=8888 -Djava.rmi.server.hostname=192.168.0.103 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar ElevenJvmApplication.jar
Tomcat的JMX配置

catalina.sh文件里最后一个JAVA_OPTS赋值语句下一行增加如下配置行:

1
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=8888 -Djava.rmi.server.hostname=192.168.50.60 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
jstack找出占用CPU最高的线程堆栈信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CpuHighTest {
public static final int initData = 666;
public static User user = new User();

public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}

public static void main(String[] args) {
CpuHighTest test = new CpuHighTest();
while (true) {
test.compute();
}
}
}

首先使用命令top -p <pid> ,显示进程的CPU使用情况

top命令查看cpu使用情况

大写的H,获取每个线程的内存情况

Top命令查看具体线程CPU使用情况

找到内存和cpu占用最高的线程tid,将线程id的十进制转为十六进制得到0x4cd0,执行jstack 19663|grep -A 10 4cd0,得到线程堆栈信息中4cd0这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法,从而查看对应的堆栈信息找出可能存在问题的代码。

jinfo

通过jinfo -flags PID查看正在运行的Java应用程序的扩展参数

1
2
3
4
5
6
Attaching to process ID 15184, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=268435456 -XX:+ManagementServer -XX:MaxHeapSize=4273995776 -XX:MaxNewSize=1424490496 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -Dcom.sun.management.jmxremote.port=8888 -Djava.rmi.server.hostname=192.168.0.103 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false

通过jinfo -sysprops PID查看java系统参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Attaching to process ID 15184, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11
com.sun.management.jmxremote.authenticate = false
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.171-b11
sun.boot.library.path = C:\Program Files\Java\jdk1.8.0_171\jre\bin
java.protocol.handler.pkgs = org.springframework.boot.loader
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = ;
java.rmi.server.randomIDs = true
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level =
sun.java.launcher = SUN_STANDARD
user.script =
user.country = CN
user.dir = D:\tmp
java.vm.specification.name = Java Virtual Machine Specification
com.sun.management.jmxremote.port = 8888
PID = 15184
java.runtime.version = 1.8.0_171-b11
java.awt.graphicsenv = sun.awt.Win32GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = C:\Program Files\Java\jdk1.8.0_171\jre\lib\endorsed

jstat

jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数] 查看堆内存各部分的使用量,以及加载类的数量

1
2
# 1s执行一次,总共执行100次,用于查看垃圾收集YGC FGC的次数变化
jstat -gcutil PID 1000 100

jstat -gc 15184

1
2
 S0C     S1C     S0U    S1U      EC       EU       OC        OU      MC        MU      CCSC     CCSU    YGC   YGCT    FGC    FGCT     GCT       
9728.0 12288.0 0.0 0.0 134656.0 32979.9 186880.0 15970.6 35496.0 33974.9 4656.0 4355.1 6 0.027 2 0.079 0.105

S0C:Survivor0区大小,单位KB
S1C:Survivor1区的大小
S0U:Survivor0区的使用大小
S1U:Survivor1区的使用大小
EC:Eden区的大小
EU:Eden区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小(元空间)
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间,单位s
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间,单位s
GCT:垃圾回收消耗总时间,单位s

堆内存统计

jstat -gccapacity 15184

1
2
NGCMN     NGCMX       NGC      S0C     S1C       EC       OGCMN      OGCMX       OGC         OC       MCMN     MCMX       MC      CCSMN    CCSMX      CCSC    YGC    FGC
87040.0 1391104.0 201216.0 9728.0 12288.0 134656.0 175104.0 2782720.0 186880.0 186880.0 0.0 1081344.0 35496.0 0.0 1048576.0 4656.0 6 2

NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0C:Survivor0区大小
S1C:Survivor1区的大小
EC:Eden区的大小
OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:当前老年代大小
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代GC次数
FGC:老年代GC次数

新生带垃圾回收统计

jstat -gcnew 15184

1
2
 S0C    S1C     S0U   S1U  TT MTT  DSS       EC        EU        YGC     YGCT
9728.0 12288.0 0.0 0.0 7 15 12288.0 134656.0 33428.7 6 0.027

S0C:Survivor0区的大小
S1C:Survivor1区的大小
S0U:Survivor0区的使用大小
S1U:Survivor1区的使用大小
TT:对象在新生代存活的次数
MTT:对象在新生代存活的最大次数
DSS:期望的幸存区大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间

新生代内存统计

jstat -gcnewcapacity 15184

1
2
 NGCMN     NGCMX     NGC      S0CMX      S0C     S1CMX     S1C       ECMX        EC       YGC   FGC
87040.0 1391104.0 201216.0 463360.0 9728.0 463360.0 12288.0 1390080.0 134656.0 6 2

NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0CMX:最大Survivor0区大小
S0C:当前Survivor0区大小
S1CMX:最大Survivor1区大小
S1C:当前Survivor1区大小
ECMX:最大伊甸园区大小
EC:当前伊甸园区大小
YGC:年轻代垃圾回收次数
FGC:老年代回收次数

老年代垃圾回收统计

jstat -gcold 15184

1
2
   MC       MU     CCSC    CCSU     OC        OU      YGC   FGC   FGCT    GCT
35496.0 33974.9 4656.0 4355.1 186880.0 15970.6 6 2 0.079 0.105

MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
OC:老年代大小
OU:老年代使用大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

老年代内存统计

jstat -gcoldcapacity 15184

1
2
 OGCMN       OGCMX        OGC         OC       YGC   FGC    FGCT     GCT
175104.0 2782720.0 186880.0 186880.0 6 2 0.079 0.105

OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:老年代大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

元数据空间统计

jstat -gcmetacapacity 15184

1
2
MCMN       MCMX        MC       CCSMN     CCSMX       CCSC     YGC   FGC   FGCT     GCT
0.0 1081344.0 35496.0 0.0 1048576.0 4656.0 6 2 0.079 0.105

MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

Arthas

Arthas是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,可以方便的定位和诊断线上程序运行问题,用java -jar运行即可,可以识别机器上所有Java进程。 这里专门准备了一个Arthas测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class Arthas {
private static HashSet hashSet = new HashSet();

public static void main(String[] args) {
// 模拟CPU过高
cpuHigh();
// 模拟线程死锁
deadThread();
// 不断向hashSet集合增加数据
addHashSetThread();
}

private static void addHashSetThread() {
new Thread(() -> {
try {
int count = 0;
while (true) {
hashSet.add("count" + count);
Thread.sleep(1000);
count++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}

private static void deadThread() {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(() -> {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " end");
}
}
}, "thread1").start();

new Thread(() -> {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " end");
}
}
}, "thread2").start();
}

private static void cpuHigh() {
new Thread(() -> {
while (true) {

}
}).start();
}
}

首先通过java -jar arthas-boot.jar启动arthas然后选择我们的对应的进程:

arthas启动

1
2
wget https://arthas.gitee.io/arthas-boot.jar
java -jar arthas-boot.jar

dashboard

dashboard可以查看整个进程的运行情况,线程、内存、GC、运行环境信息:

thread可以查看线程详情

thread加上线程ID,可以查看线程堆栈

thread -b可以查看线程死锁:

1
2
3
4
5
6
# 最繁忙的3个线程,即占用cpu最多的前3个,输出栈信息
thread -n 3
# 列出1000ms内最忙的3个线程栈
thread -n 3 -i 1000
# 查看指定状态的线程
thread --state WAITING

jad加类的全名可以反编译,这样可以方便我们查看线上代码是否是正确的版本

ognl命令可以查看线上系统变量的值,甚至可以修改变量的值: