jps、jstack、jstat、jmap、jinfo等小工具的使用以及故障排查
jps、jstack、jmap、jstat等小工具的使用以及故障排查
jps、jstack、jstat、jmap、jinfo、eclipse mat等工具使用以及故障排查
1. 前言
JDK全称Java Development Kit,除了提供核心类库,还提供了一系列的Java开发工具,例如jps、jstack、jstat、jmap等。
我们可以利用这些工具获取Java进程的pid、线程快照、内存快照、运行状态、内存使用情况等。我们合理这些小工具可以进行故障排查,例如Java进程CPU占用飙升、Java进程内存溢出OOM、Java进程发生死锁等。
2. jps命令
2.1 简介
jps命令全称Java Virtual Machine Process Status Tool,这个工具可以很方便地查看当前服务器中的Java进程。
当然了该命令是基于用户的,使用该命令无法查看到其他用户启动的Java进程。

2.2 命令
jps命令格式
jps [options]
查看一下jps命令的帮助信息
[root@localhost ~]$ jps -help
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]
Definitions:
<hostid>: <hostname>[:<port>]
常用的参数
- -q 不输出类名、Jar名和传入main方法的参数
- -m 输出传入main方法的参数
- -l 输出main类或Jar的全限名
- -v 输出传入JVM的参数
常用参数 -mlv,查看传入main方法的参数,查看main类或Jar的全限名,查看传入JVM的参数。
2.3 示例
[root@localhost ~]$ jps -mlv
21074 io.mycat.MycatStartup -DMYCAT_HOME=/home/admin/mycat-1.6.7.4 -Xms4G -Xmx4G -Xmn3G -XX:MaxPermSize=64M -XX:+AggressiveOpts -XX:MaxDirectMemorySize=4G
27323 sun.tools.jps.Jps -mlv -Denv.class.path=.:/usr/java/jdk1.8.0_131/lib/dt.jar:/usr/java/jdk1.8.0_131/lib/tools.jar:/usr/java/jdk1.8.0_131/jre/lib -Dapplication.home=/usr/java/jdk1.8.0_131 -Xms8m
第一列的数据为当前Java进程的pid。第二列为Java进程的main类或Jar的全限名、启动命令和main方法参数
3. jstack命令
3.1 简介
jstack命令可以dump当前运行JVM的线程快照。线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

3.2 命令
jstack命令格式
jstack [options] < pid >
Java进程的pid,可以由上一步的jps命令获得
先查看一下jstack命令的帮助信息
[root@localhost ~]$ jstack -help
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
附加的参数
- -F:当正常输出请求不被响应时,强制输出线程栈堆。当Java进程负载较高的时候,可以加上该参数,强制dump线程快照
- -l:除线程栈堆外,显示关于锁的附加信息
- -m:如果调用本地方法的话,可以显示c/c++的栈堆
3.3 示例
[root@localhost ~]$ jstack 21074
2020-12-10 10:41:35
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):
"pool-2-thread-1" #33 prio=5 os_prio=0 tid=0x00007f39ec778800 nid=0x5277 waiting on condition [0x00007f397e9cb000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c00493a0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
......
"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007f39ec0cb000 nid=0x525f runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
......
"VM Thread" os_prio=0 tid=0x00007f39ec078000 nid=0x5258 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f39ec01f000 nid=0x5254 runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007f39ec0d6000 nid=0x5260 waiting on condition
JNI global references: 259
打印内容的主体是线程相关的信息,线程主要分为JVM线程和用户线程。
其中JVM线程主要是后台GC线程和核心的main线程(例如GC task thread#0 (ParallelGC)),JVM线程在Java虚拟机启动时就会启动。
用户线程由用户自己创建(例如pool-2-thread-1)。
JVM内部线程包括下面几种:
- JIT编译线程: 如 C1 CompilerThread0、C2 CompilerThread0
- GC线程: 如GC Thread0、G1 Young RemSet Sampling
- 其它内部线程: 如VM Periodic Task Thread、VM Thread, Service Thread
线程的相关信息主要有线程名称、方法调用栈、线程的运行状态、线程的优先级、操作系统线程的优先级、线程16进制id等
4. jstat命令
4.1 简介
如何判断JVM是否存在内存泄露问题。如何判断JVM垃圾回收是否正常?一般的top指令基本上满足不了这样的需求,因为它主要监控的是总体的系统资源,很难定位到java应用程序。
jstat是JDK自带的一个轻量级小工具。全称Java Virtual Machine statistics monitoring tool,它位于Java的bin目录下。jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id和所选参数。

4.2 命令
jstat命令格式
jstat -< option > [-t] [-h] < vmid > [< interval > [< count >]]
option 选项详解
- class 显示classLoader 相关信息
- compiler 显示 JIT 编译相关信息
- gc 显示与 GC 相关堆信息
- gccapacity 显示各个代的容量以及使用情况
- gccause 显示垃圾收集相关信息,最后一次垃圾回收的原因
- gcnew 显示新生代信息
- gcnewcapacity 显示新生代使用情况
- gcold 显示老年代和永久的信息
- gcoldcapacity 显示老年代的大小
- gcpermcapactiy 显示永久代的大小
- gcutil 显示垃圾收集信息
- printcompilation 输出 JIT 编译的方法信息
-t 参数详解
参数可以在输出信息前面加上一个 Timestamp 列,显示程序运行的时间
-h 参数详解
参数可以周期数据输出时,输出多少行后,跟着输出一个表头信息
interval 参数详解
指定输出统计数据的周期,单位毫秒
count 参数详解
一共输出多少次数据
4.3 示例
-class命令,每秒统一次classloader信息,一共输出 2 次
[root@localhost ~]$ jstat -class 21074 1000 2
Loaded Bytes Unloaded Bytes Time
3198 6281.9 0 0.0 0.99
3198 6281.9 0 0.0 0.99
装载了3198个类,大小6281.9个字节,卸载了0个类,大小0个字节,装载和卸载耗费时间总时间0.99秒
-compiler命令,显示JVM实时编译的数量
[root@localhost ~]$ jstat -compiler 21074
Compiled Failed Invalid Time FailedType FailedMethod
3920 2 0 10.55 1 com/mysql/jdbc/AbandonedConnectionCleanupThread run
编译任务执行了3920个,失败了2个,失效了0个,编译耗费10.55秒,最后一个编译失败任务的类型为1 ,最后一个编译失败任务所在的方法
-gc命令,显示GC堆相关信息
[root@localhost ~]$ jstat -gc 21074
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
12800.0 12288.0 288.0 0.0 3120640.0 360814.7 1048576.0 17050.0 20608.0 20235.5 2432.0 2280.5 26 0.129 0 0.000 0.129
字段解释:
- S0C 年轻代中第一个S区容量(千字节)
- S1C 年轻代中第二个S区容量(千字节)
- S0U 年轻代中第一个S区已使用的空间(千字节)
- S1U 年轻代中第二个S区已使用的空间(千字节)
- EC 年轻代中E区容量(千字节)
- EU 年轻代中E区使用空间(千字节)
- OC old代的容量(千字节)
- OU old代已使用空间(千字节)
- MC metaspace元空间容量(千字节)
- MU metaspace元空间已使用容量(千字节)
- CCSC:当前压缩类空间的容量 (千字节)
- CCSU:当前压缩类空间目前已使用空间 (千字节)
- YGC 应用程序启动到采样时年轻代中GC次数
- YGCT 应用程序启动到采样时年轻代中GC所用时间
- FGC 应用程序启动到采样老年代GC次数(秒)
- FGCT 应用程序启动到采样老年代GC所用时间(秒)
- GCT 应用程序从启动到采样GC所用总时间(秒)
-gccapacity命令,显示对象使用和占用大小
[root@localhost ~]$ jstat -gccapacity 21074
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
3145728.0 3145728.0 3145728.0 12800.0 12288.0 3120640.0 1048576.0 1048576.0 1048576.0 1048576.0 0.0 1067008.0 20608.0 0.0 1048576.0 2432.0 26 0
字段解释:
- NGCMN 年轻代中初始化大小(千字节)
- NGCMX 年轻代中最大容量(千字节)
- NGC 当前年轻代容量(千字节)
- S0C 年轻代中第一个S区容量(千字节)
- S1C 年轻代中第二个S区容量(千字节)
- EC E区容量(千字节)
- OGCMN 老年代初始大小(千字节)
- OGCMX 老年代最大容量(千字节)
- OGC 老年代当前新生成的容量(千字节)
- OC 老年代容量(千字节)
- MCMN metaspace元空间初始大小(千字节)
- MCMX metaspace元空间最大容量(千字节)
- MC metaspace元空间新生成的容量(千字节)
- CCSMN 最小压缩类空间大小(千字节)
- CCSC 当前压缩类空间大小(千字节)
- YGC 从应用启动到采样年轻代中GC次数
- FGC 从应用启动到采样老年代中GC次数
-gcmetacapacity命令,查看metaspace中对象的信息
[root@localhost ~]$ jstat -gcmetacapacity 21074
MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT
0.0 1067008.0 20608.0 0.0 1048576.0 2432.0 31 0 0.000 0.134
字段解释:
- MCMN 最小元数据容量
- MCMX 最大元数据容量
- MC 当前元数据空间大小
- CCSMN 最小压缩类空间大小
- CCSMX 最大压缩类空间大小
- CCSC 当前压缩类空间大小
- YGC 从应用程序启动到采样YGC次数
- FGC 从应用程序启动到采样FULL GC次数
- FGCT 从应用程序到采样FULL GC所用时间
- GCT 从应用程序到采样GC总时间
-gcnew命令,年轻代对象信息
[root@localhost ~]$ jstat -gcnew 21074
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
8704.0 9728.0 0.0 288.0 1 15 8704.0 3125760.0 3067072.3 31 0.134
字段解释:
- S0C 年轻代中第一个S区容量
- S1C 年轻代中第二个S区容量
- S0U 年轻代中第一个S区已使用容量
- S1U 年轻代中第二个S区已使用容量
- TT 持有次数限制
- MTT 最大持有次数限制
- DSS 期望的S区大小
- EC 年轻代中E区大小
- EU 年轻代中E区已使用大小
- YGC 从应用程序启动到采样YGC次数
- YGCT 从应用程序启动到采样YGC所用时间
-gcnewcapacity命令,年轻代对象信息以及占用量
[root@localhost ~]$ jstat -gcnewcapacity 21074
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
3145728.0 3145728.0 3145728.0 1048576.0 8704.0 1048576.0 8192.0 3144704.0 3128832.0 32 0
字段解释:
- NGCMN 年轻代初始化大小
- NGCMX 年轻代最大容量
- NGC 年轻代中当前容量
- S0CMX S0最大的容量
- S0C 当前S0大小
- S1CM S1最大容量
- S1C 当前S1大小
- ECMX E区最大容量
- EC E区当前大小
- YGC 从应用程序启动到采样YGC次数
- FGC 从应用程序启动到采样FGC次数
-gcold命令,老年代对象信息
[root@localhost ~]$ jstat -gcold 21074
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
20608.0 20235.5 2432.0 2280.5 1048576.0 17234.0 32 0 0.000 0.135
字段解释:
- MC metaspace 元空间最大容量
- MU metaspace 元空间当前大小
- CCSC 压缩类空间大小
- CCSU 压缩类空间当前使用大小
- OC 老年代容量
- OU 老年代已使用大小
- YGC 从应用程序启动到采样YGC次数
- FGC 从应用程序启动到采样FGC次数
- FGCT 从应用程序启动到采样FGC耗费总时间
- GCT 从应用程序启动到采样GC耗费总时间
-gcoldcapacity命令,老年代对象占用情况
[root@localhost ~]$ jstat -gcoldcapacity 21074
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
1048576.0 1048576.0 1048576.0 1048576.0 32 0 0.000 0.135
字段解释:
- OGCMN 老年代初始化大小
- OGCMX 老年代最大容量
- OGC 老年代当前使用大小
- OC 老年代容量
-gcutil命令,统计GC情况
[root@localhost ~]$ jstat -gcutil 21074
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
2.21 0.00 54.97 1.64 98.19 93.77 32 0.135 0 0.000 0.135
字段解释:
- S0 S0区占用比
- S1 S1区占用比
- E 伊甸园区占用比
- O 老年代占用比
- M 元空间占用比
- CCS 压缩类占用比
- YGC YGC发生的次数
- YGCT 所有YGC的总共耗时
- FGC FGC发生的次数
- FGCT 所有FGC的共耗时
- GCT 所有GC的总耗时
-gccause命令,显示GC导致的原因
[root@localhost ~]$ jstat -gccause 21074
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
2.21 0.00 68.95 1.64 98.19 93.77 32 0.135 0 0.000 0.135 Allocation Failure No GC
大部分字段同-gcutil命令
字段解释:
- LGCC 上一次GC原因
- GCC 当前GC原因
-printcompilation命令,VM执行的信息
[root@localhost ~]$ jstat -printcompilation 21074
Compiled Size Type Method
3964 311 1 com/alibaba/druid/sql/visitor/SchemaStatVisitor visit
字段解释:
- compiled 编译任务的数目
- size 方法生成的字节码大小
- type 编译类型
- method 类名和方法名
5. jmap命令
5.1 简介
jmap命令是JDK中提供的一个用来监视进程运行中的JVM物理内存的占用情况的工具。该进程内存内,所有对象的情况,例如产生了哪些对象,对象数量。当系统崩溃时,jmap可以从core文件或进程中获得内存的具体匹配情况,包括Heap size、Perm size等。
使用jmap会影响线上运行的应用,所以尽量不要在线上执行此命令。如果想dump堆信息,可以使用gcore命令,比jmap -dump快。

5.2 命令
jmap命令格式
jmap [option] < pid >
查看一下jmap命令的帮助信息
[root@localhost ~]$ jmap -help
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
参数:
- option: 选项参数
- pid: 需要打印配置信息的进程ID
- executable: 产生核心dump的Java可执行文件
- core: 需要打印配置信息的核心文件
- server-id 可选的唯一id,如果相同的远程主机上运行了多台调试服务器,用此选项参数标识服务器
- remote server IP or hostname 远程调试服务器的IP地址或主机名
这些参数里面一般使用option和pid即可
option:
- no option: 查看进程的内存映像信息,类似Solaris pmap命令
- heap: 显示Java堆详细信息
- histo[:live]: 显示堆中对象的统计信息
- clstats:打印类加载器信息
- finalizerinfo: 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象
- dump::生成堆转储快照
- F: 当-dump没有响应时,使用-dump或者-histo参数。在这个模式下,live子参数无效
- help:打印帮助信息
- J:指定传递给运行jmap的JVM的参数
5.3 示例
jmap -heap pid命令,打印堆内存详细信息
打印一个堆的摘要信息,包括使用的GC算法、堆配置信息和各内存区域内存使用信息
[root@localhost ~]$ jmap -heap 21074
Attaching to process ID 21074, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration: ##堆配置情况,也就是JVM参数配置的结果
MinHeapFreeRatio = 0 ##最小堆使用比例
MaxHeapFreeRatio = 100 ##最大堆可用比例
MaxHeapSize = 4294967296 (4096.0MB) ##最大堆空间大小
NewSize = 3221225472 (3072.0MB) ##新生代分配大小
MaxNewSize = 3221225472 (3072.0MB) ##最大可新生代分配大小
OldSize = 1073741824 (1024.0MB) ##老年代大小
NewRatio = 2 ##新生代比例
SurvivorRatio = 8 ##新生代与suvivor的比例
MetaspaceSize = 21807104 (20.796875MB) ##元空间大小
CompressedClassSpaceSize = 1073741824 (1024.0MB) ##压缩类空间大小
MaxMetaspaceSize = 17592186044415 MB ##最大元空间大小
G1HeapRegionSize = 0 (0.0MB) ##G1的region大小
Heap Usage: ##堆使用情况
PS Young Generation ##新生代(伊甸Eden区 + 幸存者survior(from + to)区)
Eden Space: ##伊甸区
capacity = 3206021120 (3057.5MB) ##伊甸区容量
used = 1334298032 (1272.4857635498047MB) ##已经使用大小
free = 1871723088 (1785.0142364501953MB) ##剩余容量
41.61850412264283% used ##使用比例
From Space: ##survior1区
capacity = 7340032 (7.0MB) ##survior1区容量
used = 229376 (0.21875MB) ##surviror1区已使用情况
free = 7110656 (6.78125MB) ##surviror1区剩余容量
3.125% used ##survior1区使用比例
To Space: ##survior2 区
capacity = 6815744 (6.5MB) ##survior2区容量
used = 0 (0.0MB) ##survior2区已使用情况
free = 6815744 (6.5MB) ##survior2区剩余容量
0.0% used ##survior2区使用比例
PS Old Generation ##老年代使用情况
capacity = 1073741824 (1024.0MB) ##老年代容量
used = 17754144 (16.931671142578125MB) ##老年代已使用容量
free = 1055987680 (1007.0683288574219MB) ##老年代剩余容量
1.653483510017395% used ##老年代使用比例
7111 interned Strings occupying 559016 bytes. ##系统中使用的字符串总大小
jmap -histo:live pid命令,显示堆中对象的统计信息
其中包括每个Java类、对象数量、内存大小(单位:字节)、完全限定的类名。打印的虚拟机内部的类名称将会带有一个*前缀。如果指定了live子选项,则只计算活动的对象。
[root@localhost ~]$ jmap -histo:live 21074
#编号 #实例个数 #实例总大小 #全限定类名
num #instances #bytes class name
----------------------------------------------
1: 262144 6291456 org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapper
2: 16536 2830928 [C
3: 2622 1197536 [Ljava.lang.Object;
4: 1547 1122888 [I
5: 896 675144 [B
6: 16462 395088 java.lang.String
7: 3483 393304 java.lang.Class
8: 20149 322384 java.lang.Integer
9: 8820 282240 io.mycat.statistic.HeartbeatRecorder$Record
10: 8954 214896 java.util.concurrent.ConcurrentLinkedQueue$Node
11: 2007 211264 [Ljava.util.HashMap$Node;
12: 5444 174208 java.util.HashMap$Node
13: 4183 133856 java.util.concurrent.ConcurrentHashMap$Node
14: 1493 119440 io.mycat.route.RouteResultsetNode
15: 1959 94032 java.util.HashMap
16: 2 80560 [Ljava.lang.Integer;
17: 93 67352 [Ljava.util.concurrent.ConcurrentHashMap$Node;
18: 3713 59408 java.lang.Object
......
类名解释如下
- B byte
- C char
- D double
- F float
- I int
- J long
- Z boolean
- [ 数组,如[I表示int[]
- [L+类名 其他对象
jmap -dump:format=b,file=heapdump.hprof pid命令,dump当前内存快照
以hprof二进制格式转储Java堆到指定filename的文件中。live子选项是可选的。如果指定了live子选项,堆中只有活动的对象会被转储。想要浏览heap dump,你可以使用jhat(Java堆分析工具)或者MAT等工具读取生成的文件。
[root@localhost ~]$ jmap -dump:format=b,file=heapdum.hprof 21074
Dumping heap to /home/admin/heapdum.hprof ...
Heap dump file created
这里可以使用Java VisualVM工具打开dump文件,该工具位于JAVA_HOME/bin路径下。

双击打开jvisualvm.exe软件
点击文件,选择装入
选择对应的dump文件,等待一段时间后就可以显示dump文件中的内容了
显示dump文件的内容

或者是使用Eclipse Memory Analyzer(下载地址)内存分析软件打开。这里最好下载独立安装版本
6. jinfo命令
6.1 简介
有些时候我们希望查看Java进程的某些JVM参数,或者实时修改某些JVM参数,那么jinfo命令可以帮我们做到这点
6.2 命令
jinfo命令格式
jinfo [options] < pid >
先查看一下jinfo命令的帮助信息
C:\Users\yanggu>jinfo --help
Usage:
jinfo <option> <pid>
(to connect to a running process)
where <option> is one of:
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
<no option> to print both VM flags and system properties
-? | -h | --help | -help to print this help message
jinfo命令主要分为两类,查看和修改配置
查看配置命令
jinfo -sysprops PID:查看系统参数,Java代码通过System.getProperties(“参数名”)获取。 通过设置JVM参数(java -Dkey=value -jar xxx.jar),可以改变系统参数jinfo -flags PID:查看曾经赋值过的一些参数jinfo -flag 具体参数名 PID:查看对应参数的值
修改配置命令
jinfo -flag +|- 具体参数名 PID:针对boolean类型jinfo -flag 具体参数名=具体参数值 PID:针对非boolean类型
另外需要注意的是,并不是所有的参数都支持实时修改
查看支持动态修改的参数:java -XX:+PrintFlagsFinal -version | grep manageable,参数是manageable才能够进行动态修改
6.3 示例
- 查看系统参数
jinfo -sysprops PID
- 查看赋值的参数
jinfo -flags PID
- 查看对应参数的值
jinfo -flag 具体参数名 PID

4. 针对boolean类型参数进行修改jinfo -flag +|- 具体参数名 PID
5. 针对非boolean类型参数进行修改jinfo -flag key=value PID
7. 实战一:Java进程CPU占用高
基本思路:
- 使用
top命令查看哪个Java进程CPU占用高,获得对应Java进程的pid - 使用
top -H -pid命令,查看当前Java进程哪个线程CPU占用高,获取对应的线程id - 使用
printf '%x\n' threadid命令,打印出线程id的16进制值,后面的\n是用于换行的 - 使用
jstack pid命令,打印出当前java进程的线程快照 - 使用
grep '16进制线程id' -A 20命令,根据线程id的16进制值进行过滤,定位到事发线程快照的后20行
下面是具体的实现思路
使用top命令,查看进程状况,发现进程id为12345的Java进程CPU百分比最高。
[root@localhost ~]$ top
top - 10:59:22 up 272 days, 20 min, 5 users, load average: 0.40, 0.31, 0.26
Tasks: 196 total, 1 running, 191 sleeping, 4 stopped, 0 zombie
%Cpu(s): 0.7 us, 0.2 sy, 0.0 ni, 98.5 id, 0.6 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32781452 total, 253684 free, 23170896 used, 9356872 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 9159436 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 root 20 0 7558288 1.616g 6304 S 135.0 5.2 4788:27 java
使用top -H -p 12345命令查看使用进程内使用CPU最高的线程id 2665
[root@localhost ~]$ top -H -p 12345
top - 11:01:12 up 272 days, 22 min, 5 users, load average: 0.17, 0.24, 0.24
Threads: 207 total, 0 running, 207 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.0 us, 0.1 sy, 0.0 ni, 97.7 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32781452 total, 239320 free, 23172284 used, 9369848 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 9158784 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2665 root 20 0 7558472 1.616g 6408 S 135.0 5.2 187:00.91 java
使用printf '%x\n' threadid命令,打印出线程id的16进制值
[root@localhost ~]$ printf '%x\n' 2665
a69
查看一下grep命令的帮助文档
[root@localhost ~]$ grep --help
Context control:
-B, --before-context=NUM print NUM lines of leading context
-A, --after-context=NUM print NUM lines of trailing context
-C, --context=NUM print NUM lines of output context
grep '线程id的16进制值' -A 20 显示出匹配行的后20行。
使用jstack 12345 | grep ‘a69’ -A 20,打印出对应线程的快照。
[root@localhost ~]$ jstack 9631 | grep 'a69' -A 20
"System Clock" #174 daemon prio=5 os_prio=0 tid=0x00007fa824045000 nid=0xa69 runnable [0x00007fa81aafa000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000005d4f89660> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
获得到对应的事发线程快照之后,查看对应的方法调用栈,定位到异常代码,进行故障排查。
也可以使用脚本直接定位show-busy-java-thread
8. 实战二:Java进程报OOM异常
8.1 什么是OOM
OOM为out of memory的简称,来源于java.lang.OutOfMemoryError,指线程运行时需要的内存空间大于系统分配的内存空间,就会报OOM错误
8.2 导致OOM的原因
为什么会没有内存了呢?原因不外乎有两点:
- 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
- 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
内存泄漏和内存溢出的区别与联系
- 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。强引用所指向的对象不会被回收,可能导致内存泄漏,虚拟机宁愿抛出OOM也不会去回收他指向的对象。
- 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
- 内存泄漏是内存溢出发生的原因之一。
8.3 可能发生OOM的情况
最常见的OOM情况有以下三种:
java.lang.OutOfMemoryError: Java heap spacejava堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。java.lang.OutOfMemoryError: PermGen space或java.lang.OutOfMemoryError:MetaSpaceJava方法区或者是Java8元空间溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m-XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。java.lang.StackOverflowError不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
8.4 排查手段
- 使用
jmap -heap pid命令查看Java进程堆内存使用情况 - 使用
jstat -gc pid命令查看Java进程堆和gc情况 - 使用
jmap -dump:format=b,file=heapdump.phrof pid命令,dump下当前Java进程的内存快照 - 使用
jstack pid命令,dump下当前Java进程的线程快照。 - 根据报错信息判断是堆内存溢出还是方法区溢出
8.5 分析思路
-
先通过内存映像工具对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。
-
如果是内存泄漏,可进一步通过工具查看泄漏对象到
GC Roots的引用链。这样就能够找到泄漏的对象是通过怎么样的路径与GC Roots相关联的导致垃圾回收机制无法将其回收。掌握了泄漏对象的类信息和GC Roots引用链的信息,就可以比较准确地定位泄漏代码的位置。 -
如果不存在泄漏,那么就是内存中的对象确实必须存活着,那么此时就需要通过虚拟机的堆参数(
-Xmx和-Xms)来适当调大参数。 -
从代码上检查是否存在某些对象存活时间过长、持有时间过长的情况,尝试减少运行时内存的消耗。
8.6 实战
接下来用一个简单的案例,展示OOM问题排查过程
代码编写
public class OomDemo {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
while(true){
stringBuilder.append(System.currentTimeMillis());
}
}
}
执行代码时,通过设置JVM参数达到OOM的目的
推荐使用-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath参数分别用于指定发生OOM是否要导出堆以及导出堆的文件路径。
当前Java项目的启动命令java -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=oom.hprof OomDemo
idea中为Java进程设置JVM参数
-
点击运行,然后取消运行,生成运行配置信息

-
打开配置信息

-
将JVM参数复制到VM options中,然后点击OK
-Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=oom.hprof
以上命令执行后,程序会出现如下错误,且Java进程停止
java.lang.OutOfMemoryError: Java heap space
Dumping heap to oom.hprof ...
Heap dump file created [3815425 bytes in 0.012 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:673)
at java.lang.StringBuilder.append(StringBuilder.java:214)
at com.yanggu.jvm.jmm.OomDemo.main(OomDemo.java:7)
使用MAT分析
首先找到VM参数中配置的dump内存快照文件,然后使用MAT打开hprof文件,选择报告里的泄露嫌疑分析Leak Suspects Report
可以看到有一个本地变量,站了总存储的82%,实际占用的是char[],See stacktrace,可看到该对象所在线程的堆栈信息:
通过这儿,可以定位到发生OOM的代码段,至此,可根据具体代码具体分析。
9. 实战三:Java进程发生死锁
9.1 什么是死锁
死锁是指两个或两个以上的进程或线程,在执行过程中,由于竞争资源而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去

9.2 死锁产生的有4个必要条件
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
9.3 如何预防和避免死锁
预防
死锁的预防基本思想打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
比如:
- 打破互斥条件:允许进程同时访问某些资源
- 打破不剥夺条件:允许进程从占有者占有的资源中强行剥夺一些资源
- 打破请求与保持条件:进程在运行前一次性地向系统申请它所需要的全部资源
- 打破循环等待条件:实行资源有序分配策略
避免
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
9.4 死锁案例演示
死锁代码,然后运行Java项目
public class DeadLock {
//创建两个对象,用两个线程分别先后独占
private Boolean flag1 = true;
private Boolean flag2 = false;
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(() -> {
System.out.println("线程1开始,作用是当flag1 = true 时,将flag2也改为 true");
synchronized (deadLock.flag1){
if(deadLock.flag1){
try{
//睡眠1s ,模拟业务执行耗时,并保证两个线程进入死锁状态
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("flag1 = true,准备锁住flag2...");
synchronized (deadLock.flag2){
deadLock.flag2 = true;
}
}
}
}).start();
new Thread(() -> {
System.out.println("线程2开始,作用是当flag2 = false 时,将flag1也改为 false");
synchronized (deadLock.flag2){
if(!deadLock.flag2){
try{
//睡眠1s ,模拟业务执行耗时,并保证两个线程进入死锁状态
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("flag2 = false,准备锁住flag1...");
synchronized (deadLock.flag1){
deadLock.flag1 = false;
}
}
}
}).start();
}
}
打开对应Java源码所在的cmd窗口

使用jps和jstack命令,查看死锁信息
#使用jps-ml命令找到对应的java进程
D:\Project\jvm\src\main\java\com\yanggu\jvm\jmm>jps -ml
11396 com.yanggu.jvm.jmm.DeadLock
#使用jstack -l pid命令dump下对应的线程快照
D:\Project\jvm\src\main\java\com\yanggu\jvm\jmm>jstack -l 11396
2020-12-11 15:12:25
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.66-b18 mixed mode):
......
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000007065398 (object 0x000000076b979258, a java.lang.Boolean),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000007067c28 (object 0x000000076b979268, a java.lang.Boolean),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.yanggu.jvm.jmm.DeadLock.lambda$main$1(DeadLock.java:41)
- waiting to lock <0x000000076b979258> (a java.lang.Boolean)
- locked <0x000000076b979268> (a java.lang.Boolean)
at com.yanggu.jvm.jmm.DeadLock$$Lambda$2/2003749087.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at com.yanggu.jvm.jmm.DeadLock.lambda$main$0(DeadLock.java:23)
- waiting to lock <0x000000076b979268> (a java.lang.Boolean)
- locked <0x000000076b979258> (a java.lang.Boolean)
at com.yanggu.jvm.jmm.DeadLock$$Lambda$1/1283928880.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
这里可以看到Thread-0和Thread-1分别持有对方的锁,同时都在等待对方的锁,因此造成了死锁。这里可以修改代码然后重启项目。
更多推荐



所有评论(0)