本次介绍一下在排查问题以及JVM调优的时候,常用的一些工具。
1. 示例项目
首先创建一个示例项目,用于演示工具的使用。
1.1 项目整体结构图
1.2 项目pom文件
4.0.0
org.example
WebJVM
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.1.2.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
1.3 配置文件application.yml
server:
port: 8080
1.4 启动类Application
package com.jvm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2. JPS
本次在Mac环境上演示这些工具。首先要查看Java程序进程号,可以用jps命令
运行示例项目,并在命令行输入jps命令。
jps
可知29321为本次运行的进程。
3. Jmap
jmap命令是一个可以输出所有内存中对象的工具,甚至可以将JVM中的heap,以二进制输出成文本
3.1 命令格式
jmap [option]
(to connect to running process) 连接到正在运行的进程
jmap [option]
(to connect to a core file) 连接到核心文件
jmap [option] [server_id@]
(to connect to remote debug server) 连接到远程调试服务
3.2 参数说明
3.2.1 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服务;
3.2.2 histo
此命令可以查看内存信息、实例个数以及占用内存大小。
jmap -histo 29321 > ./log.txt
该命令可以将结果输出到log.txt文件中。打开该文件,如下图:
可以看到有四列,分别代表:
- num:序号
- instances:实例数量
- bytes:占用空间大小
- class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]
3.2.3 heap
该命令可以查看堆信息,这里特别说明一下,由于实验机是Mac,而Mac系统在JDK8的版本上有BUG,导致对当前进程进行监控时,当前进程会退出,所以Java在JDK9之后加入了jhsdb前缀命令进行了修复。所以下边说明会分普通版本和Mac版本。
普通版本:
jmap -heap 29251
Mac版本:
jhsdb jmap --heap --pid 29591
得到下图:
可以看到JVM采用的垃圾收集器是G1,有G1的一些信息。
3.2.4 dump命令
记录堆内存快照
jmap ‐dump:format=b,file=dump.hprof 30381
也可以设置内存溢出自动导出dump文件(文件很大的时候,可能会导不出来)
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=./ (路径)
示例代码:
package com.zyz.oom;
import com.zyz.User;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* JVM设置
* ‐Xms10M ‐Xmx10M ‐XX:+PrintGCDetails ‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=/Users/zhangyunzheng/jvm.dump
*/
public class OOMTest {
public static void main(String[] args) {
List
可以用jvisualvm工具导入该dump文件分析
4. Jstack
4.1 监控死锁
用该工具可以查找死锁,见如下示例代码
package com.zyz.deadLock;
import java.util.Date;
public class DeadLockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
static class LockA implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + " LockA 开始执行");
while (true) {
synchronized (DeadLockTest.obj1) {
System.out.println(new Date().toString() + " LockA 锁住 obj1");
Thread.sleep(3000); // 此处等待是给B能锁住机会
synchronized (DeadLockTest.obj2) {
System.out.println(new Date().toString() + " LockA 锁住 obj2");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class LockB implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + " LockB 开始执行");
while (true) {
synchronized (DeadLockTest.obj2) {
System.out.println(new Date().toString() + " LockB 锁住 obj2");
Thread.sleep(3000); // 此处等待是给A能锁住机会
synchronized (DeadLockTest.obj1) {
System.out.println(new Date().toString() + " LockB 锁住 obj1");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行该程序,执行jstack命令
jstack 30578
"Thread-1" 线程名
prio=5 优先级=5
tid=0x00007fef9082d000 线程id
nid=0x5f03 线程对应的本地线程标识nid
java.lang.Thread.State: BLOCKED 线程状态
4.2 找出占用cpu最高的线程堆栈信息
步骤如下:
- 使用命令top -p
,显示你的java进程的内存情况,pid是你的java进程号,比如19663 - 按H,获取每个线程的内存情况
- 找到内存和cpu占用最高的线程tid,比如19664
- 转为十六进制得到 0x4cd0,此为线程id的十六进制表示
- 执行 jstack 19663|grep -A 10 4cd0,得到线程堆栈信息中 4cd0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调 用方法
- 查看对应的堆栈信息找出可能存在问题的代码
5. Jinfo
查看正在运行的Java应用程序的扩展参数
5.1 查看jvm的参数
jinfo -flags 30383
5.2 查看java系统参数
jinfo -sysprops 30383
6. Jstat
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。
6.1 命令格式
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
6.2 垃圾回收统计
jstat -gc pid 最常用,可以评估程序内存使用及GC压力整体情况
- S0C:第一个幸存区的大小,单位KB
- S1C:第二个幸存区的大小
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- OC:老年代大小
- OU:老年代使用大小
- MC:方法区大小(元空间)
- MU:方法区使用大小
- CCSC:压缩类空间大小
- CCSU:压缩类空间使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间,单位s
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间,单位s
- GCT:垃圾回收消耗总时间,单位s
6.3 堆内存统计
jstat -gccapacity 30634
- NGCMN:新生代最小容量
- NGCMX:新生代最大容量
- NGC:当前新生代容量
- S0C:第一个幸存区大小
- S1C:第二个幸存区的大小
- EC:伊甸园区的大小
- OGCMN:老年代最小容量
- OGCMX:老年代最大容量
- OGC:当前老年代大小
- OC:当前老年代大小
- MCMN:最小元数据容量
- MCMX:最大元数据容量
- MC:当前元数据空间大小
- CCSMN:最小压缩类空间大小
- CCSMX:最大压缩类空间大小
- CCSC:当前压缩类空间大小
- YGC:年轻代gc次数
- FGC:老年代GC次数
6.4 新生代垃圾回收统计
jstat -gcnew 30634
- S0C:第一个幸存区的大小
- S1C:第二个幸存区的大小
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- TT:对象在新生代存活的次数
- MTT:对象在新生代存活的最大次数
- DSS:期望的幸存区大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间
6.5 新生代内存统计
jstat -gcnewcapacity 30634
- NGCMN:新生代最小容量
- NGCMX:新生代最大容量
- NGC:当前新生代容量
- S0CMX:最大幸存1区大小
- S0C:当前幸存1区大小
- S1CMX:最大幸存2区大小
- S1C:当前幸存2区大小
- ECMX:最大伊甸园区大小
- EC:当前伊甸园区大小
- YGC:年轻代垃圾回收次数
- FGC:老年代回收次数
6.6 老年代垃圾回收统计
jstat -gcold 30634
- MC:方法区大小 MU:方法区使用大小
- CCSC:压缩类空间大小
- CCSU:压缩类空间使用大小
- OC:老年代大小
- OU:老年代使用大小
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
6.7 老年代内存统计
jstat -gcoldcapacity 30634
- OGCMN:老年代最小容量
- OGCMX:老年代最大容量
- OGC:当前老年代大小
- OC:老年代大小
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
6.8 元空间数据统计
jstat -gcmetacapacity 30634
- MCMN:最小元数据容量
- MCMX:最大元数据容量
- MC:当前元数据空间大小
- CCSMN:最小压缩类空间大小
- CCSMX:最大压缩类空间大小
- CCSC:当前压缩类空间大小
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
6.9 查看GC情况
jstat -gcutil 30634
- S0:幸存1区当前使用比例
- S1:幸存2区当前使用比例
- E:伊甸园区使用比例
- O:老年代使用比例
- M:元数据区使用比例
- CCS:压缩使用比例
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
7. Arthas
Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,可以方便的定位和诊断 线上程序运行问题。Arthas 官方文档十分详细,详见:https://alibaba.github.io/arthas
PS:没想到阿里的大佬也是魔兽迷,十分推荐用这个工具排查问题