主要介绍 jps
、jstat
、jinfo
、jmap
、jhat
、jstack
等几个JDK命令行工具
(JDK1.8 HotSpot)
用工具来分析数据,以此来获取结果。下面包含四个数据依据。
heapdump
/javacore文件)threaddump
/hprof文件)在JDK的bin
目录下有很多监视虚拟机和故障处理的工具。文章将讲解多个关于JDK的命令行工具。
描述: 虚拟机进程状态管理工具;其他工具在使用时需要通过该工具来获取
LVMID
;
Local Virtaul Machine Identifer
LVMID)注
: options (选项) ; 本文将 options 理解为工具的参数。
jps 命令格式: jps [ options ] [hostid]
参数 | 描述 |
---|---|
-l | 输出主类的全名,如果是执行的jar,则输出jar的路径 |
-v | 输出JVM启动时的JVM参数 |
当然常用参数还是 -l
,主要是为了获得ID值。
编写一个应用程序,然后通过
JPS
命令来查阅信息
public class TestJPS {
public static void main(String[] args) {
while(true){
}
}
}
C:\Program Files\Java\jdk1.8.0_141\bin>jps -l
10048 sun.tools.jps.Jps
6832 org.jetbrains.jps.cmdline.Launcher
976
7092 com.intellij.rt.execution.application.AppMain
4536 org/netbeans/Main
3484
可以获取的到JVMID
, 然后用再使用其他命令获取该JMID
的详细信息
注
:JDK1.8 针对这个-l
参数有是有变化的。
监视虚拟机各种运行状态信息的命令行工具;
它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
,在没有GUI图形化界面,只提供纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。(jstat工具参数还是比较多,信息还是比较丰富)
jstat [options] vmid [interval [s|ms] [count]] ] (远程命令格式略有不同)
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
参数 | 功能作用 |
---|---|
-class | 监视类装载、卸载数量,以及总空间和装载耗时等 |
-gc |
监视堆中 eden、 survivor 、老年代、元空间等空间大小和已使用情况,GC次数、耗时等 |
-gcmetacapacity |
元空间 |
-gcutil | 与gc 类似,但是注重的是占比情况 |
-printcompilation | 输出已经被JIT重新编译的方法 |
-gcoldcapacity | 老年代统计信息 |
-gcnew | 新生代 |
-gccause |
与-gcutil相似,但是会输出上一次GC的原因 |
通过实验来验证jstat相关参数。
使用-gc 命令; 为了能够更加直观,在程序中设置了VM相关参数; 然后运行、查看结果并分析。
/**
*
设置初始堆为20M,最大堆为20M,新生代10M. from大小为2M,to大小为2M,eden大小为6M
-verbose:gc -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=3 -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails -XX:+PrintTenuringDistribution
*/
public class TestJPS {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3,allocation4;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
//第一次Minor GC, eden区空间不足,将 allocation2 分配到老年代,
//allocation1 被分配到 from区,allocation1的大小在from区能够装下
//且没有满足动态年龄判定的条件
allocation4 = new byte[4 * _1MB];
//第二次GC,Minor GC 原因 eden去空间不足。allocation3被分配到老年代,
//allocation1 被分配到 to 区, allocation1 的大小能够在to区装下,
//同时没有达到动态年龄判定的条件。所以能够存活进入to区。
while(true){
}
}
}
(二)jstat -gc 运行并查看结果
(二)结果分析
参数 | 解析 |
---|---|
S0C | surivor(s0)区域大小 |
s1c | s1区大小 |
S0U | S0的使用大小 |
S1U | S1的使用大小 |
EC | eden可以使用的大小 |
EU | eden已经使用 |
OC | 老年代可以使用的大小 |
OU | 老年代已经使用的带下 |
MC | 元空间可以使用的大小 |
MU | 元空间已经使用的大小 |
CCSC | 压缩类空间大小 |
CCSU | 压缩类已经使用大小 |
YGC | 年轻代垃圾回收次数 |
YGCT | 年轻代垃圾回收总耗时 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收总耗时 |
GCT | 垃圾回收消耗总时间 |
(四) GC打印日志
程序去掉循环运行获取GC日志信息。
[GC (Allocation Failure) [DefNew
Desired survivor size 1048576 bytes, new threshold 15 (max 15)
- age 1: 895224 bytes, 895224 total
: 5776K->874K(8192K), 0.0117018 secs] 5776K->4970K(18432K), 0.0117932 secs] [Times: user=0.02 sys=0.02, real=0.01 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 1048576 bytes, new threshold 15 (max 15)
- age 2: 895136 bytes, 895136 total
: 4970K->874K(8192K), 0.0098978 secs] 9066K->9066K(18432K), 0.0099464 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
Heap
def new generation total 8192K, used 5031K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 6144K, 67% used [0x00000000fec00000, 0x00000000ff00f748, 0x00000000ff200000)
from space 2048K, 42% used [0x00000000ff200000, 0x00000000ff2da8a0, 0x00000000ff400000)
to space 2048K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff600000)
tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00020, 0x00000000ffe00200, 0x0000000100000000)
Metaspace used 3199K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 349K, capacity 386K, committed 512K, reserved 1048576K
总结: GC日志信息与Jstat -gc 打印信息一致。
-class 参数; 监视类装载、卸载数量、总空间以及装载所耗费的时间。
(一)执行命令
与实验一样使用相同代码
(二)结果分析
参数 | 解析 |
---|---|
Loaded | 加载class的数量 |
Bytes | 占用空间大小 |
Unloaded | 未加载数量 |
Bytes | 未加载占用空间 |
Time | 时间 |
元数据空间统计(-gcmetacapacity)
(一)执行命令
(二)结果分析
参数 | 描述 |
---|---|
MCMN | 最小元数据容量 |
MCMX | 最大元数据容量 |
MC | 当前元数据空间大小 |
CCSMN | 最小压缩类空间大小 |
CCSMX | 最大压缩类空间大小 |
CCSC | 当前压缩类空间大小 |
YGC | 年轻代垃圾回收次数 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收消耗时间 |
GCT | 垃圾回收消耗总时间 |
-gcutil
监视内容与-gc
基本相同,但输出主要关注已使用空间占总空间的百分比。
(一)执行命令
参数理解与上面相同。
使用命令
-gccause
, 与gcutil 相似,但是会输出上一次GC发生的原因
(一)执行命令
如果直接使用文本控制台来监控,这个jstat
工具还是笔记不错的。
注:
其他参数不做实验,可自行做实验去验证。
jinfo
工具, Java配置信息工具; 通过命令可以查看配置信息参数
参数格式: jinfo [option] ipd
在控制台输入 jinfo ,则会提示相关命令参数,可借助提示执行相关命令。
-flag to print the value of the named VM flag
-flag [+|-] to enable or disable the named VM flag
-flag = to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
to print both of the above
(一)执行命令
C:\Program Files\Java\jdk1.8.0_141\bin>jinfo -sysprops 7424
参数-sysprops 与 System.getProperites()
代码都能获取进程信息
该工具命令参数还是比较丰富、能够获取比较详细的信息。
jmap
(JVM Memory Map for java)命令用于生成堆转储快照
; 当然还可以使用-XX:HeapDumpOnOutOfMemoryError
参数,可以让虚拟机在OOM异常之后自动生产dump文件。
jmap [ option ] vmid
选项 | 作用 |
---|---|
-dump |
生成Java堆转储快照。 格式 -dump:[live,]format=b,file=< filename >,其中live子参数说明是否只dump出存活的对象 |
-finalizerinfo |
显示F-Queue中等待Finalizer线程执行finalize方法的对象 |
-heap |
显示java堆详细信息,如使用哪种收集器、参数配置、分代状况等 |
-histo |
显示堆中对象统计信息,包括类,实例数量、合计容量 |
-F |
当虚拟机进程对 -dump选项没有响应时,可使用这个选项强制生成dump快照 |
通过实验去验证
jmap
相关参数信息
(一)-dump命令参数使用
通过这个命令, 可以 生成java堆转储快照
(二)源码
/**
* 设置初始堆为20M,最大堆为20M,新生代10M. from大小为2M,to大小为2M,eden大小为6M
-verbose:gc -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=3 -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails -XX:+PrintTenuringDistribution
*/
public class TestJPS {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3,allocation4;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
//第一次Minor GC, eden区空间不足,将 allocation2 分配到老年代,
//allocation1 被分配到 from区,allocation1的大小在from区能够装下
//且没有满足动态年龄判定的条件
allocation4 = new byte[4 * _1MB];
//第二次GC,Minor GC 中 eden去空间不足。allocation3被分配到老年代,
//allocation1 被分配到 to 区, allocation1 的大小能够在to区装下,
//同时没有达到动态年龄判定的条件。所以能够存活进入to区。
while(true){
}
}
}
(三)执行命令
C:\Program Files\Java\jdk1.8.0_141\bin>jmap -dump:format=b,file=jmapdump.hprof 9044
Dumping heap to C:\Program Files\Java\jdk1.8.0_141\bin\jmapdump.bin ...
Heap dump file created
生成文件,然后对文件进行分析
(四)分析
使用visualVM打开文件,然后对文件进行分析。
"Reference Handler"
,"Finalizer"
,"main"
,| 排在最前用"" 是线程的名称|daemon
|线程类型, 属于线程类型,此处是属于daemon(守护线程)|prio
|线程优先级|tid
|jvm线程id通过java.lang.Thread.getId()获取|WAITING
, RUNNABLE
|线程状态|(五)补充
常见的一些应用场景
现象 | 原因 |
---|---|
cpu使用率不高但是响应很慢 |
进行dump,查看是否有很多thread struck在了i/o、数据库等地方,定位瓶颈原因 |
请求无法响应 |
多次dump,对比是否所有的runnable线程都一直在执行相同的方法,如果是的,可能是锁了! |
使用finalizerinfo 参数
(一)执行命令
C:\Program Files\Java\jdk1.8.0_141\bin>jmap -finalizerinfo 9484
Attaching to process ID 9484, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
Number of objects pending for finalization: 0
使用-heap ,显示java堆详细信息
(一)源码
package com.jvm;
/**
* 设置初始堆为20M,最大堆为20M,新生代10M. from大小为2M,to大小为2M,eden大小为6M
-verbose:gc -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails -XX:+PrintTenuringDistribution
*/
public class TestJPS {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3,allocation4;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
//第一次Minor GC, eden区空间不足,将 allocation2 分配到老年代,
//allocation1 被分配到 from区,allocation1的大小在from区能够装下
//且没有满足动态年龄判定的条件
allocation4 = new byte[4 * _1MB];
//第二次GC,Minor GC 中 eden去空间不足。allocation3被分配到老年代,
//allocation1 被分配到 to 区, allocation1 的大小能够在to区装下,
//同时没有达到动态年龄判定的条件。所以能够存活进入to区。
while(true){
}
}
}
(二)执行命令
运行打印结果:
C:\Program Files\Java\jdk1.8.0_141\bin>jmap -heap 11220
Attaching to process ID 11220, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 20971520 (20.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 10485760 (10.0MB)
OldSize = 10485760 (10.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 9437184 (9.0MB)
used = 4249104 (4.0522613525390625MB)
free = 5188080 (4.9477386474609375MB)
45.025126139322914% used
Eden Space:
capacity = 8388608 (8.0MB)
used = 4249016 (4.052177429199219MB)
free = 4139592 (3.9478225708007812MB)
50.652217864990234% used
From Space:
capacity = 1048576 (1.0MB)
used = 88 (8.392333984375E-5MB)
free = 1048488 (0.9999160766601562MB)
0.008392333984375% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
tenured generation:
capacity = 10485760 (10.0MB)
used = 9279920 (8.850021362304688MB)
free = 1205840 (1.1499786376953125MB)
88.50021362304688% used
1732 interned Strings occupying 155648 bytes.
(三)参数解析
数据项 | 描述 |
---|---|
using thread-local object allocation | 启用线程本地对象分配 |
Mark Sweep Compact GC | 使用’标记整理’收集器 |
Heap Configuration: | 堆配置 |
Heap Usage: | 堆使用 |
CompressedClassSpaceSize | |
MaxMetaspaceSize | |
G1HeapRegionSize |
(四) 什么是 CompressedClassSpaceSize
UseCompressedOops
, 所谓OOPS
是指ordinary object pointers
,就是原始指针。UseCompressedOops
选项。打开后,OOPS
变成了32bit。UseCompressedOops
之外,额外增加了一个新选项叫做·UseCompressedClassPointer·。Compressed
版本。而这些指针指向的空间被称作Compressed Class Space
。默认大小是1G,但可以通过CompressedClassSpaceSize
调整如果你的java程序引用了太多的包,有可能会造成这个空间不够用,于是会看到
java.lang.OutOfMemoryError: Compressed class space
这时,一般调大CompreseedClassSpaceSize
就可以了
使用
histo
命令
(一)执行命令
C:\Program Files\Java\jdk1.8.0_141\bin>jmap -histo 11220
num #instances #bytes class name
----------------------------------------------
1: 31 12870640 [B
2: 2588 331360 [C
3: 621 70688 java.lang.Class
4: 2411 57864 java.lang.String
5: 146 39264 [I
6: 595 34296 [Ljava.lang.Object;
7: 838 33520 java.util.TreeMap$Entry
8: 116 8352 java.lang.reflect.Field
9: 198 8072 [Ljava.lang.String;
10: 150 4800 java.util.Hashtable$Entry
11: 73 4672 java.net.URL
12: 104 4160 java.lang.ref.SoftReference
13: 256 4096 java.lang.Integer
14: 122 3904 java.util.HashMap$Node
15: 96 3072 java.util.concurrent.ConcurrentHashMap$Node
......省略下面的更多的信息
-F
当对-dump 生成文件时没有发反应,可以使用该命令强制生成dump快照。(实验结果存在问题,不做描述)
不推荐使用,有更好的方案;也不做介绍了。
jstack
(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump
或者javacore
文件);如线程间死锁
、死循环
、请求外部资源导致的长时间等待等
都是导致线程长时间停顿的常见原因、jstack
来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做了些什么事情,或者等待着什么资源jstack [ option ] vmid
对
jstatck
的选项参数进行列举,并描述其作用
参数 | 作用 |
---|---|
-F |
当正常输出的请求不被响应时,强制输出线程堆栈 |
-l |
除堆栈外,显示关于锁的附加信息 |
-m |
如果调用本地方法的话,可以显示 c/C++的堆栈 |
使用 jstack -l 参数
(一)执行命令
C:\Program Files\Java\jdk1.8.0_141\bin>jstack -l 11220
2018-04-20 15:48:03
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):
"Monitor Ctrl-Break" #10 daemon prio=5 os_prio=0 tid=0x00000000534ec800 nid=0x2780 runnable [0x0000000053c8e000]
java.lang.Thread.State: RUNNABLE
at java.net.DualStackPlainSocketImpl.accept0(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
- locked <0x00000000ff000c68> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at com.intellij.rt.execution.application.AppMain$1.run(AppMain.java:79)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000005282e800 nid=0x2c20 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x00000000527ff800 nid=0x21cc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x00000000527b6000 nid=0x2038 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x00000000527b0800 nid=0x2988 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000005279e800 nid=0x8c4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000005279d000 nid=0x2a28 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000052770800 nid=0x2a30 in Object.wait() [0x0000000052d3e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000ffa15ac0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000ffa15ac0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
Locked ownable synchronizers:
- None
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000052729000 nid=0x2d6c in Object.wait() [0x0000000052bff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000ffa15c78> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000ffa15c78> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Locked ownable synchronizers:
- None
"main" #1 prio=5 os_prio=0 tid=0x0000000002548000 nid=0x2a20 runnable [0x0000000002a7e000]
java.lang.Thread.State: RUNNABLE
at com.jvm.TestJPS.main(TestJPS.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Locked ownable synchronizers:
- None
"VM Thread" os_prio=2 tid=0x0000000052720800 nid=0x28d4 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000000534a8800 nid=0x2a94 waiting on condition
JNI global references: 15
(二)分析
这个命令与 jmap -dump:format=b,file=jmapdump.hprof 都能打印线程信息。
参数 | 解析 |
---|---|
nid | 系统线程id(NativeThread ID)nid=0xaef,和top命令查看的线程pid对应,不过一个是10进制,一个是16进制 |
其他信息可以参考前面的jmap快照进行学习
理论知识是实践的指导工具,将知识应用到实际项目才是真正的目的。