Appearance
概述
性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。
Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络 I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
体会 1:使用数据说明问题,使用知识分析问题,使用工具处理问题。
体会 2:无监控、不调优。
简单命令行工具
在我们刚接触 java 学习的时候,大家肯定最先了解的两个命令就是 javac
,java
,那么除此之外,还有没有其他的命令可以供我们使用呢?
我们进入到安装 jdk 的 bin 目录,发现还有一系列辅助工具。这些辅助工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决 Java 应用程序的一些疑难杂症。
官方源码地址:
http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools
jps:查看正在运行的 Java 进程
jps(Java Process Status):显示指定系统内所有的 HotSpot 虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
说明:对于本地虚拟机进程来说,进程的本地虚拟机 ID 与操作系统的进程 ID 是一致的,是唯一的。
基本使用语法为:jps [options] [hostid]
。
代码示例
java
public class ScannerTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String info = scanner.next();
}
}
运行后,在命令行输入 jps 查看进程:
我们还可以通过追加参数,来打印额外的信息。
options 参数
- -q:仅仅显示 LVMID(local virtual machine id),即本地虚拟机唯一 id。不显示主类的名称等
- -l:输出应用程序主类的全类名或如果进程执行的是 jar 包,则输出 jar 完整路径
- -m:输出虚拟机进程启动时传递给主类 main() 的参数
- -v:列出虚拟机进程启动时的 JVM 参数。比如:
-Xms20m -Xmx50m
是启动程序指定的 jvm 参数
说明:以上参数可以综合使用。
如图:
补充:如果某 Java 进程关闭了默认开启的 UsePerfData 参数(即使用参数 -XX:-UsePerfData
),那么 jps 命令(以及下面介绍的 jstat)将无法探知该 Java 进程。
hostid 参数
RMI 注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd。
对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到 IP 地址欺诈攻击。
如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行 jstatd 服务器,而是在本地使用 jstat 和 jps 工具。
jstat:查看 JVM 统计信息
官方文档:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
基本使用语法为:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
。
查看命令相关参数:jstat-h
或 jstat-help
。
其中 vmid 是进程 id 号,也就是 jps 之后看到的前面的号码,如下:
option参数
选项 option 可以由以下值构成:
类装载相关的:
- -class:显示 ClassLoader 的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
垃圾回收相关的:
- -gc:显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息
- -gccapacity:显示内容与
-gc
基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间 - -gcutil:显示内容与
-gc
基本相同,但输出主要关注已使用空间占总空间的百分比 - -gccause:与
-gcutil
功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因 - -gcnew:显示新生代 GC 状况
- -gcnewcapacity:显示内容与
-gcnew
基本相同,输出主要关注使用到的最大、最小空间 - -geold:显示老年代 GC 状况
- -gcoldcapacity:显示内容与
-gcold
基本相同,输出主要关注使用到的最大、最小空间 - -gcpermcapacity:显示永久代使用到的最大、最小空间
JIT 相关的:
-compiler:显示 JIT 编译器编译过的方法、耗时等信息
-printcompilation:输出已经被 JIT 编译的方法
jstat -class
jstat -compiler
显示 JIT 编译器编译过的方法、耗时等信息。
jstat -printcompilation
输出已经被 JIT 编译的方法。
jstat -gc
显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息。
执行代码:
java
public class GCTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
byte[] arr = new byte[1024 * 100];//100KB
list.add(arr);
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
JVM 参数:
java
-Xms60m -Xmx60m -XX:SurvivorRatio=8
运行后利用命令查询:
表头 | 含义(字节) |
---|---|
S0C | 幸存者 0 区的大小 |
S1C | 幸存者 1 区的大小 |
S0U | 幸存者 0 区已使用的大小 |
S1U | 幸存者 1 区已使用的大小 |
EC | Eden 区的大小 |
EU | Eden 区已使用的大小 |
OC | 老年代的大小 |
OU | 老年代已使用的大小 |
MC | 元空间的大小 |
MU | 元空间已使用的大小 |
CCSC | 压缩类空间的大小 |
CCSU | 压缩类空间已使用的大小 |
YGC | 从应用程序启动到采样时 Young GC 的次数 |
YGCT | 从应用程序启动到采样时 Young GC 消耗时间(秒) |
FGC | 从应用程序启动到采样时 Full GC 的次数 |
FGCT | 从应用程序启动到采样时的 Full GC 的消耗时间(秒) |
GCT | 从应用程序启动到采样时 GC 的总时间 |
后面的参数代表 1000 毫秒打印一次,一个打印 10 次。
jstat -gccapacity
显示内容与 -gc
基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间。
jstat -gcutil
显示内容与 -gc
基本相同,但输出主要关注已使用空间占总空间的百分比。
表头 | 含义(字节) |
---|---|
SO | Survivor 0 区空间百分比 |
S1 | Survivor 1 区空间百分比 |
E | Eden 区空间百分比 |
O | Old 区空间百分比 |
N | 方法区空间百分比 |
CCS | 压缩空间百分比 |
YGC | 从应用程序启动到采样时 Young GC 的次数 |
YGCT | 从应用程序启动到采样时 Young GC 消耗时间(秒) |
FGC | 从应用程序启动到采样时 Full GC 的次数 |
FGCT | 从应用程序启动到采样时的 Full GC 的消耗时间(秒) |
GCT | 从应用程序启动到采样时 GC 的总时间 |
jstat -gccause
与 -gcutil
功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因。
jstat -gcnew
显示新生代 GC 状况。
jstat -gcnewcapacity
显示内容与 -gcnew
基本相同,输出主要关注使用到的最大、最小空间。
jstat -gcold
显示老年代 GC 状况。
jstat -gcoldcapacity
显示内容与 -gcold
基本相同,输出主要关注使用到的最大、最小空间。
其他参数
下面的参数都是配合 option 参数后面使用。
基本使用语法为:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
。
interval 参数:用于指定输出统计数据的周期,单位为毫秒。即:查询间隔
count 参数:用于指定查询的总次数
-t 参数:可以在输出信息前加上一个 Timestamp 列,显示程序的运行时间。单位:秒
我们可以比较 Java 进程的启动时间以及总 GC 时间(GCT 列),或者两次测量的间隔时间以及总 GC 时间的增量,来得出 GC 时间占运行时间的比例。
如果该比例超过 20%,则说明目前堆的压力较大;如果该比例超过 90%,则说明堆里几乎没有可用空间,随时都可能抛出 OOM 异常。
-h 参数:可以在周期性数据输出时,输出多少行数据后输出一个表头信息
jstat -t
可以在输出信息前加上一个 Timestamp 列,显示程序的运行时间。单位:秒。
jstat -t -h
可以在周期性数据输出时,输出多少行数据后输出一个表头信息。
其他功能
jstat 还可以用来判断是否出现内存泄漏:
第 1 步:在长时间运行的 Java 程序中,我们可以运行 jstat 命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值
第 2 步:然后,我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。如果这些值呈上涨趋势,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏
jinfo:实时查看和修改 JVM 配置参数
jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。在很多情况卡,Java 应用程序不会指定所有的 Java 虚拟机参数。而此时,开发人员可能不知道某一个具体的 Java 虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了 jinfo 工具,开发人员可以很方便地找到 Java 虚拟机参数的当前值。
jinfo 不仅可以查看运行时某一个 Java 虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。
但是,并非所有参数都支持动态修改。参数只有被标记为 manageable 的 flag 可以被实时修改。其实,这个修改能力是极其有限的。
基本使用语法为:jinfo [options] pid
。
说明:java 进程 ID 必须要加上。
选项 | 选项说明 |
---|---|
no option | 输出全部的参数和系统属性 |
-flag name | 输出对应名称的参数 |
-flag [+-]name | 开启或者关闭对应名称的参数 只有被标记为 manageable 的参数才可以被动态修改 |
-flag name=value | 设定对应名称的参数 |
-flags | 输出全部的参数 |
-sysprops | 输出系统属性 |
参数查看
jinfo -sysprops
properties
> jinfo -sysprops
jboss.modules.system.pkgs = com.intellij.rt
java.vendor = Oracle Corporation
sun.java.launcher = SUN_STANDARD
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
catalina.useNaming = true
os.name = Windows 10
...
jinfo -flags
shell
C:\Users\24560>jps
13968 GCTest
22016 Launcher
8912
17740 Jps
C:\Users\24560>jinfo -flags 13968
# 输出结果如下:
Attaching to process ID 13968, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.231-b11
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=62914560 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=20971520 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=20971520 -XX:OldSize=41943040 -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -Xms60m -Xmx60m -XX:SurvivorRatio=8 -javaagent:F:\Programming area\Ideal\IntelliJ IDEA 2020.3\lib\idea_rt.jar=55919:F:\Programming area\Ideal\IntelliJ IDEA 2020.3\bin -Dfile.encoding=UTF-8
jinfo -flag name
查看当前的 Java 程序是否使用了指定的参数。
shell
> jinfo -flag UseParallelGC 13968
-XX:+UseParallelGC
> jinfo -flag UseG1GC 13968
-XX:-UseG1GC
参数修改
jinfo -flag [+-]name
开启或者关闭对应名称的参数 只有被标记为 manageable 的参数才可以被动态修改。
shell
> jinfo -flag +PrintGCDetails 13968
> jinfo -flag PrintGCDetails 13968
-XX:+PrintGCDetails
> jinfo -flag -PrintGCDetails 13968
> jinfo -flag PrintGCDetails 13968
-XX:-PrintGCDetails
参数拓展
java -XX:+PrintFlagsInitial
查看所有 JVM 参数启动的初始值。
sh
[Global flags]
intx ActiveProcessorCount = -1 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
...
java -XX:+PrintFlagsFinal
查看所有 JVM 参数的最终值。
java -XX:+PrintFlagsFinal -version | grep manageable
:可以查看被标记为 manageable 的参数。
manageable 参数就是在 Java 程序启动后还能重新设置的参数,如 GC 就不能在启动程序后再重新修改,所以不是 manageable 参数。
sh
[Global flags]
intx ActiveProcessorCount = -1 {product}
...
intx CICompilerCount := 4 {product}
uintx InitialHeapSize := 333447168 {product}
uintx MaxHeapSize := 1029701632 {product}
uintx MaxNewSize := 1774714880 {product}
java -XX:+PrintCommandLineFlags
查看哪些已经被用户或者 JVM 设置过的详细的 XX 参数的名称和值。
sh
-XX:InitialHeapSize=332790016 -XX:MaxHeapSize=5324640256 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
jmap:导出内存映像文件&内存使用情况
官方帮助文档:
https://docs.oracle.com/en/java/javase/11/tools/jmap.html
jmap(JVM Memory Map):作用一方面是获取 dump 文件(堆转储快照文件,二进制文件),它还可以获取目标 Java 进程的内存相关信息,包括 Java 堆各区域的使用情况、堆中对象的统计信息、类加载信息等。开发人员可以在控制台中输入命令 jmap -help
查阅 jmap 工具的具体使用方式和一些标准选项配置。
基本语法
基本使用语法为:
jmap [option] <pid>
jmap [option] <executable <core>
jmap [option] [server_id@] <remote server IP or hostname>
选项 | 作用 |
---|---|
-dump | 生成 dump 文件(Java 堆转储快照),-dump:live 只保存堆中的存活对象 |
-heap | 输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息,以及内存的使用信息等 |
-histo | 输出堆空间中对象的统计信息,包括类、实例数量和合计容量,-histo:live 只统计堆中的存活对象 |
-J <flag> | 传递参数给 jmap 启动的 jvm |
-finalizerinfo | 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象,仅 linux/solaris 平台有效 |
-permstat | 以 ClassLoader 为统计口径输出永久代的内存状态信息,仅 linux/solaris 平台有效 |
-F | 当虚拟机进程对-dump 选项没有任何响应时,强制执行生成 dump 文件,仅 linux/solaris 平台有效 |
-h | -help | jmap 工具使用的帮助命令 |
-j <flag> | 传递参数给 jmap 启动的 JVM |
说明:这些参数和 linux 下输入显示的命令多少会有不同,包括也受 JDK 版本的影响。
导出映像文件
手动方式
手动生成 dump 文件。相关的两个命令如下:
shell
jmap -dump:format=b,file=<filename.hprof> <pid>>
jmap -dump:live,format=b,file=<filename.hprof> <pid>
使用如图:
在 D 盘生成了 4 个文件,其中 4.hprof 用了第二个命令生成。
文件的大小依次递增,因为运行越久,里面的对象越多,占用的空间越大。
建议使用 jmap -dump:live,format=b,file=<filename.hprof> <pid>
命令生成 dump 文件,因为只需要获取存活的对象,没必要把被回收或者死亡的对象 dump 到文件里,导致文件太大。
自动方式
当程序发生 OOM 退出系统时,一些瞬时信息都随着程序的终止而消失,而重现 OOM 问题往往比较困难或者耗时。此时若能在 OOM 时,自动导出 dump 文件就显得非常迫切。
这里介绍一种比较常用的取得堆快照文件的方法,即使用:
-XX:HeapDumpOnOutOfMemoryError
:在程序发生 OOM 时,导出应用程序的当前堆快照。
-XX:HeapDumpPath=<filename.hprof>
:可以指定堆快照的保存位置。
代码示例如下:
java
public class GCTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
byte[] arr = new byte[1024 * 100];//100KB
list.add(arr);
try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
设置 JVM 参数:
java
-Xms60m -Xmx60m -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=D:\5.hprof
运行代码后,当发生 OOM 异常,则立马在 D 盘生成 5.hprof 文件。
显示堆内存相关信息
两个常用命令:
jmap -heap pid
jmap -histo pid
命令行示例:
sh
jps
17744 Launcher
1904 GCTest
8912
15316 Jps
jmap -heap 3540 > a.txt
jmap -histo 1904 > b.txt
两个命令将信息分别写到了 a.txt、b.txt 文件。
a.txt 内容为:
java
Attaching to process ID 1904, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.231-b11
using thread-local object allocation.
Parallel GC with 10 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4255121408 (4058.0MB)
NewSize = 88604672 (84.5MB)
MaxNewSize = 1418199040 (1352.5MB)
OldSize = 177733632 (169.5MB)
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 = 66584576 (63.5MB)
used = 31136256 (29.69384765625MB)
free = 35448320 (33.80615234375MB)
46.761964812992126% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 177733632 (169.5MB)
used = 0 (0.0MB)
free = 177733632 (169.5MB)
0.0% used
3163 interned Strings occupying 259640 bytes.
b.txt 内容:
java
num #instances #bytes class name
----------------------------------------------
1: 1610 44293784 [B
2: 659 2906760 [I
3: 7774 906192 [C
4: 5933 142392 java.lang.String
5: 691 79112 java.lang.Class
6: 1305 67680 [Ljava.lang.Object;
7: 791 31640 java.util.TreeMap$Entry
8: 628 25120 java.util.LinkedHashMap$Entry
9: 456 22168 [Ljava.lang.String;
10: 364 11648 java.util.HashMap$Node
11: 37 11504 [Ljava.util.HashMap$Node;
12: 152 10944 java.lang.reflect.Field
13: 438 10512 java.lang.StringBuilder
14: 242 7744 java.util.Hashtable$Entry
15: 233 7456 java.io.File
16: 101 6464 java.net.URL
17: 241 5784 java.lang.StringBuffer
18: 125 5000 java.lang.ref.SoftReference
19: 258 4128 java.lang.Integer
20: 117 3744 java.util.concurrent.ConcurrentHashMap$Node
21: 25 3648 [Ljava.util.Hashtable$Entry;
22: 75 3600 java.nio.HeapCharBuffer
23: 74 3552 java.nio.HeapByteBuffer
24: 42 3360 [S
25: 8 3008 java.lang.Thread
26: 5 2568 [J
27: 20 2496 [Ljava.util.concurrent.ConcurrentHashMap$Node;
28: 44 2464 sun.misc.URLClassPath$JarLoader
29: 2 2384 [[Ljava.lang.Object;
30: 89 2136 java.net.Parts
31: 2 2080 [[C
32: 26 2080 java.lang.reflect.Constructor
33: 39 1872 sun.util.locale.LocaleObjectCache$CacheEntry
34: 43 1720 java.lang.ref.Finalizer
35: 34 1632 java.util.HashMap
36: 1 1568 [[B
37: 37 1480 java.io.ObjectStreamField
38: 26 1456 java.lang.Class$ReflectionData
39: 91 1456 java.lang.Object
40: 20 1280 java.util.concurrent.ConcurrentHashMap
41: 15 1200 [Ljava.util.WeakHashMap$Entry;
42: 45 1080 sun.misc.URLClassPath$3
43: 2 1064 [Ljava.lang.invoke.MethodHandle;
44: 11 1056 java.util.jar.JarFile$JarFileEntry
45: 1 1040 [Ljava.lang.Integer;
46: 41 984 java.io.ExpiringCache$Entry
47: 11 880 java.util.zip.ZipEntry
48: 22 880 sun.util.locale.BaseLocale$Key
49: 47 840 [Ljava.lang.Class;
50: 15 840 sun.nio.cs.UTF_8$Encoder
51: 34 816 sun.security.action.GetPropertyAction
52: 12 768 java.util.jar.JarFile
53: 12 752 [Ljava.lang.reflect.Field;
54: 15 720 java.util.WeakHashMap
55: 22 704 java.lang.ref.ReferenceQueue
56: 17 680 java.util.WeakHashMap$Entry
57: 12 672 java.util.zip.ZipFile$ZipFileInputStream
58: 19 608 java.util.Locale
59: 19 608 sun.util.locale.BaseLocale
60: 14 560 sun.nio.cs.UTF_8$Decoder
61: 22 528 java.util.Locale$LocaleKey
62: 13 520 java.security.AccessControlContext
63: 21 504 java.util.jar.Attributes$Name
64: 18 432 sun.misc.MetaIndex
65: 10 400 java.io.FileDescriptor
66: 13 392 [Ljava.io.ObjectStreamField;
67: 1 384 com.intellij.rt.execution.application.AppMainV2$1
68: 1 384 java.lang.ref.Finalizer$FinalizerThread
69: 24 384 java.lang.ref.ReferenceQueue$Lock
70: 6 384 java.nio.DirectByteBuffer
71: 16 384 java.util.ArrayList
72: 12 384 java.util.zip.ZipCoder
73: 1 376 java.lang.ref.Reference$ReferenceHandler
74: 4 352 java.lang.reflect.Method
75: 6 336 java.nio.DirectLongBufferU
76: 7 336 java.util.Hashtable
77: 7 336 java.util.Hashtable$Enumerator
78: 6 336 java.util.LinkedHashMap
79: 14 336 sun.misc.FileURLMapper
80: 6 336 sun.nio.cs.ext.DoubleByte$Decoder
81: 10 320 java.lang.OutOfMemoryError
82: 10 320 java.util.Vector
83: 3 312 [D
84: 13 312 [Ljava.lang.reflect.Constructor;
85: 13 312 java.lang.Class$1
86: 13 312 sun.reflect.NativeConstructorAccessorImpl
87: 12 288 java.util.ArrayDeque
88: 6 288 java.util.Properties
89: 3 280 [[Ljava.lang.String;
90: 5 280 java.util.ResourceBundle$CacheKey
91: 5 280 sun.util.calendar.ZoneInfo
92: 3 240 [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;
93: 3 232 [Ljava.net.URL;
94: 7 224 java.lang.ThreadLocal$ThreadLocalMap$Entry
95: 7 224 java.security.CodeSource
96: 14 224 sun.misc.URLClassPath$JarLoader$1
97: 9 216 java.util.LinkedList$Node
98: 2 208 sun.net.www.protocol.file.FileURLConnection
99: 13 208 sun.reflect.DelegatingConstructorAccessorImpl
100: 5 200 java.security.ProtectionDomain
101: 3 192 [Ljava.io.File;
102: 4 192 java.io.BufferedReader
103: 6 192 java.io.FileInputStream
104: 8 192 java.net.URLClassLoader$1
105: 4 192 java.util.TreeMap
106: 4 192 sun.nio.cs.StreamDecoder
107: 4 192 sun.nio.cs.StreamEncoder
108: 3 192 sun.nio.cs.ext.DoubleByte$Encoder
109: 2 192 sun.util.calendar.Gregorian$Date
110: 4 160 java.io.BufferedWriter
111: 4 160 java.lang.ClassLoader$NativeLibrary
112: 4 160 java.lang.ClassNotFoundException
113: 4 160 java.security.PrivilegedActionException
114: 5 160 java.util.LinkedList
115: 5 160 java.util.ResourceBundle$LoaderReference
116: 5 160 sun.util.locale.provider.LocaleProviderAdapter$Type
117: 3 144 java.util.StringTokenizer
118: 6 144 sun.misc.PerfCounter
119: 3 144 sun.misc.URLClassPath
120: 6 144 sun.usagetracker.UsageTrackerClient$1
121: 2 128 java.io.ExpiringCache$1
122: 4 128 java.lang.StringCoding$StringDecoder
123: 4 128 java.lang.StringCoding$StringEncoder
124: 4 128 java.util.Stack
125: 4 128 java.util.concurrent.ConcurrentHashMap$ForwardingNode
126: 3 120 java.io.BufferedInputStream
127: 1 120 java.net.SocksSocketImpl
128: 5 120 java.util.Collections$UnmodifiableRandomAccessList
129: 5 120 sun.misc.FloatingDecimal$PreparedASCIIToBinaryBuffer
130: 7 112 java.lang.Class$3
131: 2 112 java.lang.Package
132: 2 112 java.util.zip.ZipFile$ZipFileInflaterInputStream
133: 2 112 sun.nio.cs.ISO_8859_1$Encoder
134: 3 96 java.io.FileOutputStream
135: 3 96 java.io.FilePermission
136: 4 96 java.io.OutputStreamWriter
137: 4 96 java.lang.RuntimePermission
138: 2 96 java.lang.ThreadGroup
139: 3 96 java.lang.ref.WeakReference
140: 3 96 java.util.ArrayList$Itr
141: 6 96 java.util.HashSet
142: 2 96 java.util.ResourceBundle$BundleReference
143: 2 96 java.util.zip.Inflater
144: 1 96 sun.misc.Launcher$AppClassLoader
145: 3 96 sun.misc.URLClassPath$JarLoader$2
146: 3 96 sun.net.spi.DefaultProxySelector$NonProxyInfo
147: 1 88 java.net.DualStackPlainSocketImpl
148: 1 88 sun.misc.Launcher$ExtClassLoader
149: 5 80 [Ljava.security.Principal;
150: 2 80 [Lsun.util.locale.provider.LocaleProviderAdapter$Type;
151: 2 80 java.io.ExpiringCache
152: 5 80 java.io.FileInputStream$1
153: 5 80 java.lang.ClassLoader$3
154: 5 80 java.lang.ThreadLocal
155: 1 80 java.net.URI
156: 5 80 java.security.ProtectionDomain$Key
157: 2 80 java.util.ServiceLoader$LazyIterator
158: 2 80 sun.misc.FloatingDecimal$BinaryToASCIIBuffer
159: 2 80 sun.misc.URLClassPath$1
160: 2 80 sun.misc.URLClassPath$2
161: 2 80 sun.util.locale.LanguageTag
162: 3 72 [Ljava.lang.reflect.Method;
163: 3 72 java.lang.ThreadLocal$ThreadLocalMap
164: 3 72 java.net.Proxy$Type
165: 3 72 java.util.Arrays$ArrayList
166: 3 72 java.util.Collections$SynchronizedSet
167: 1 72 java.util.ResourceBundle$RBClassLoader
168: 3 72 java.util.concurrent.atomic.AtomicLong
169: 3 72 sun.misc.FloatingDecimal$ExceptionalBinaryToASCIIBuffer
170: 1 72 sun.util.locale.provider.JRELocaleProviderAdapter
171: 1 64 [F
172: 2 64 [Ljava.lang.Thread;
173: 2 64 java.io.DataInputStream
174: 2 64 java.io.FileNotFoundException
175: 2 64 java.io.PrintStream
176: 2 64 java.lang.ClassValue$Entry
177: 2 64 java.lang.NoSuchMethodError
178: 2 64 java.lang.VirtualMachineError
179: 2 64 java.lang.ref.ReferenceQueue$Null
180: 2 64 java.security.BasicPermissionCollection
181: 2 64 java.security.Permissions
182: 2 64 java.util.LinkedHashMap$LinkedEntryIterator
183: 2 64 java.util.ServiceLoader
184: 4 64 sun.reflect.ReflectionFactory$1
185: 2 48 [Ljava.util.Enumeration;
186: 2 48 java.io.BufferedOutputStream
187: 2 48 java.io.ByteArrayOutputStream
188: 2 48 java.io.File$PathStatus
189: 3 48 java.io.FilePermission$1
190: 2 48 java.io.FilePermissionCollection
191: 2 48 java.io.FileReader
192: 2 48 java.io.InputStreamReader
193: 2 48 java.net.InetAddress$Cache
194: 2 48 java.net.InetAddress$Cache$Type
195: 1 48 java.net.SocketInputStream
196: 2 48 java.net.URLClassLoader$3
197: 2 48 java.nio.charset.CoderResult
198: 3 48 java.nio.charset.CodingErrorAction
199: 2 48 java.util.Date
200: 1 48 java.util.Properties$LineReader
201: 2 48 java.util.ServiceLoader$1
202: 2 48 java.util.zip.ZStreamRef
203: 2 48 sun.instrument.InstrumentationImpl$1
204: 2 48 sun.misc.CompoundEnumeration
205: 2 48 sun.misc.NativeSignalHandler
206: 2 48 sun.misc.Signal
207: 2 48 sun.net.www.MessageHeader
208: 3 48 sun.net.www.ParseUtil
209: 3 48 sun.net.www.protocol.jar.Handler
210: 2 48 sun.nio.cs.Surrogate$Parser
211: 1 48 sun.nio.cs.US_ASCII$Decoder
212: 3 48 sun.reflect.ReflectionFactory$GetReflectionFactoryAction
213: 1 48 sun.util.locale.provider.LocaleResources$ResourceReference
214: 1 48 sun.util.resources.TimeZoneNames
215: 1 48 sun.util.resources.en.TimeZoneNames_en
216: 1 40 java.util.ResourceBundle$1
217: 1 40 sun.nio.cs.StandardCharsets$Aliases
218: 1 40 sun.nio.cs.StandardCharsets$Cache
219: 1 40 sun.nio.cs.StandardCharsets$Classes
220: 1 40 sun.nio.cs.ext.ExtendedCharsets
221: 1 32 [Ljava.lang.OutOfMemoryError;
222: 2 32 [Ljava.lang.StackTraceElement;
223: 1 32 [Ljava.lang.ThreadGroup;
224: 1 32 [Ljava.net.Proxy$Type;
225: 1 32 java.io.ByteArrayInputStream
226: 1 32 java.io.WinNTFileSystem
227: 1 32 java.lang.ArithmeticException
228: 2 32 java.lang.Boolean
229: 2 32 java.lang.ClassLoader$2
230: 1 32 java.lang.InternalError
231: 1 32 java.lang.NullPointerException
232: 1 32 java.net.InetAddress$InetAddressHolder
233: 1 32 java.net.Socket
234: 1 32 java.net.URI$Parser
235: 2 32 java.net.URLClassLoader$3$1
236: 2 32 java.nio.ByteOrder
237: 2 32 java.util.Hashtable$KeySet
238: 2 32 java.util.LinkedHashMap$LinkedEntrySet
239: 2 32 java.util.LinkedHashMap$LinkedKeySet
240: 2 32 java.util.concurrent.atomic.AtomicInteger
241: 1 32 java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl
242: 1 32 java.util.jar.Manifest$FastInputStream
243: 1 32 sun.instrument.InstrumentationImpl
244: 1 32 sun.misc.FloatingDecimal$ASCIIToBinaryBuffer
245: 2 32 sun.misc.PostVMInitHook$1
246: 1 32 sun.misc.URLClassPath$FileLoader$1
247: 1 32 sun.net.spi.DefaultProxySelector$3
248: 1 32 sun.nio.cs.StandardCharsets
249: 2 32 sun.usagetracker.UsageTrackerClient$2
250: 2 32 sun.usagetracker.UsageTrackerClient$3
251: 1 32 sun.util.locale.provider.LocaleResources
252: 1 32 sun.util.locale.provider.LocaleServiceProviderPool
253: 1 24 [Ljava.io.File$PathStatus;
254: 1 24 [Ljava.lang.ClassValue$Entry;
255: 1 24 [Ljava.net.InetAddress$Cache$Type;
256: 1 24 [Ljava.net.InetAddress;
257: 1 24 [Ljava.security.ProtectionDomain;
258: 1 24 [Lsun.launcher.LauncherHelper;
259: 1 24 java.lang.Class$MethodArray
260: 1 24 java.lang.ClassValue$Version
261: 1 24 java.lang.invoke.MethodHandleImpl$4
262: 1 24 java.lang.reflect.ReflectPermission
263: 1 24 java.net.Inet4Address
264: 1 24 java.net.Inet6AddressImpl
265: 1 24 java.net.InetSocketAddress$InetSocketAddressHolder
266: 1 24 java.net.Proxy
267: 1 24 java.util.BitSet
268: 1 24 java.util.Collections$EmptyMap
269: 1 24 java.util.Collections$SetFromMap
270: 1 24 java.util.Collections$UnmodifiableCollection$1
271: 1 24 java.util.Locale$Cache
272: 1 24 java.util.ResourceBundle$Control$CandidateListCache
273: 1 24 java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl$1
274: 1 24 java.util.jar.Manifest
275: 1 24 sun.instrument.TransformerManager
276: 1 24 sun.launcher.LauncherHelper
277: 1 24 sun.misc.JarIndex
278: 1 24 sun.misc.Launcher$AppClassLoader$1
279: 1 24 sun.misc.URLClassPath$FileLoader
280: 1 24 sun.nio.cs.ISO_8859_1
281: 1 24 sun.nio.cs.ThreadLocalCoders$1
282: 1 24 sun.nio.cs.ThreadLocalCoders$2
283: 1 24 sun.nio.cs.US_ASCII
284: 1 24 sun.nio.cs.UTF_16
285: 1 24 sun.nio.cs.UTF_16BE
286: 1 24 sun.nio.cs.UTF_16LE
287: 1 24 sun.nio.cs.UTF_8
288: 1 24 sun.nio.cs.ext.GBK
289: 1 24 sun.reflect.NativeMethodAccessorImpl
290: 1 24 sun.usagetracker.UsageTrackerClient$4
291: 1 24 sun.util.locale.BaseLocale$Cache
292: 1 24 sun.util.locale.provider.SPILocaleProviderAdapter$1
293: 1 24 sun.util.locale.provider.TimeZoneNameProviderImpl
294: 1 24 sun.util.resources.LocaleData$1
295: 1 16 [Ljava.lang.Throwable;
296: 1 16 [Ljava.security.cert.Certificate;
297: 1 16 [Lsun.instrument.TransformerManager$TransformerInfo;
298: 1 16 [Lsun.util.calendar.ZoneInfoFile$ZoneOffsetTransitionRule;
299: 1 16 java.io.FileDescriptor$1
300: 1 16 java.io.FileOutputStream$1
301: 1 16 java.lang.CharacterDataLatin1
302: 1 16 java.lang.ClassValue$Identity
303: 1 16 java.lang.Compiler$1
304: 1 16 java.lang.Runtime
305: 1 16 java.lang.String$CaseInsensitiveComparator
306: 1 16 java.lang.System$2
307: 1 16 java.lang.SystemClassLoaderAction
308: 1 16 java.lang.Terminator$1
309: 1 16 java.lang.invoke.MemberName$Factory
310: 1 16 java.lang.invoke.MethodHandleImpl$1
311: 1 16 java.lang.invoke.MethodHandleImpl$2
312: 1 16 java.lang.invoke.MethodHandleImpl$3
313: 1 16 java.lang.invoke.MethodHandleStatics$1
314: 1 16 java.lang.ref.Reference$1
315: 1 16 java.lang.ref.Reference$Lock
316: 1 16 java.lang.reflect.ReflectAccess
317: 1 16 java.net.AbstractPlainSocketImpl$1
318: 1 16 java.net.InetAddress$1
319: 1 16 java.net.InetAddress$2
320: 1 16 java.net.InetSocketAddress
321: 1 16 java.net.PlainSocketImpl$1
322: 1 16 java.net.Socket$2
323: 1 16 java.net.SocksSocketImpl$3
324: 1 16 java.net.URLClassLoader$7
325: 1 16 java.nio.Bits$1
326: 1 16 java.nio.charset.Charset$ExtendedProviderHolder$1
327: 1 16 java.nio.charset.CoderResult$1
328: 1 16 java.nio.charset.CoderResult$2
329: 1 16 java.security.ProtectionDomain$2
330: 1 16 java.security.ProtectionDomain$JavaSecurityAccessImpl
331: 1 16 java.util.Collections$EmptyIterator
332: 1 16 java.util.Collections$EmptyList
333: 1 16 java.util.Collections$EmptySet
334: 1 16 java.util.Hashtable$EntrySet
335: 1 16 java.util.ResourceBundle$Control
336: 1 16 java.util.ResourceBundle$RBClassLoader$1
337: 1 16 java.util.TimeZone$1
338: 1 16 java.util.WeakHashMap$KeySet
339: 1 16 java.util.concurrent.atomic.AtomicBoolean
340: 1 16 java.util.jar.Attributes
341: 1 16 java.util.jar.JavaUtilJarAccessImpl
342: 1 16 java.util.zip.ZipFile$1
343: 1 16 sun.misc.ASCIICaseInsensitiveComparator
344: 1 16 sun.misc.FloatingDecimal$1
345: 1 16 sun.misc.Launcher
346: 1 16 sun.misc.Launcher$BootClassPathHolder$1
347: 1 16 sun.misc.Launcher$ExtClassLoader$1
348: 1 16 sun.misc.Launcher$Factory
349: 1 16 sun.misc.Perf
350: 1 16 sun.misc.Perf$GetPerfAction
351: 1 16 sun.misc.PostVMInitHook$2
352: 1 16 sun.misc.Unsafe
353: 1 16 sun.net.NetProperties$1
354: 1 16 sun.net.spi.DefaultProxySelector
355: 1 16 sun.net.spi.DefaultProxySelector$1
356: 1 16 sun.net.www.protocol.file.Handler
357: 1 16 sun.reflect.DelegatingMethodAccessorImpl
358: 1 16 sun.reflect.ReflectionFactory
359: 1 16 sun.security.action.GetBooleanAction
360: 1 16 sun.usagetracker.UsageTrackerClient
361: 1 16 sun.util.calendar.Gregorian
362: 1 16 sun.util.calendar.ZoneInfoFile$1
363: 1 16 sun.util.calendar.ZoneInfoFile$Checksum
364: 1 16 sun.util.locale.provider.AuxLocaleProviderAdapter$NullProvider
365: 1 16 sun.util.locale.provider.JRELocaleProviderAdapter$1
366: 1 16 sun.util.locale.provider.SPILocaleProviderAdapter
367: 1 16 sun.util.locale.provider.TimeZoneNameUtility$TimeZoneNameGetter
368: 1 16 sun.util.resources.LocaleData
369: 1 16 sun.util.resources.LocaleData$LocaleDataResourceBundleControl
Total 24100 48649392
其他作用
jmap -permstat pid
查看系统的 ClassLoader 信息。
jmap -finalizerinfo pid
查看堆积在 finalizer 队列中的对象。
小结
由于 jmap 将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap 需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由 jmap 导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。
举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live 选项将无法探知到这些对象。
另外,如果某个线程长时间无法跑到安全点,jmap 将一直等下去。与前面讲的 jstat 则不同,垃圾回收器会主动将 jstat 所需要的摘要数据保存至固定位置之中,而 jstat 只需直接读取即可。
jhat:JDK 自带堆分析工具
jhat(JVM Heap Analysis Tool):Sun JDK 提供的 jhat 命令与 jmap 命令搭配使用,用于分析 jmap 生成的 heap dump 文件(堆转储快照)。jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
使用了 jhat 命令,就启动了一个 http 服务,端口是 7000,即 http://localhost:7000/
,就可以在浏览器里分析。
说明:jhat 命令在 JDK9、JDK10 中已经被删除,官方建议用 VisualVM 代替。
基本适用语法:jhat <option> <dumpfile>
option 参数 | 作用 |
---|---|
-stack false | true | 关闭|打开对象分配调用栈跟踪 |
-refs false | true | 关闭|打开对象引用跟踪 |
-port port-number | 设置 jhat HTTP Server 的端口号,默认 7000 |
-exclude exclude-file | 执行对象查询时需要排除的数据成员 |
-baseline exclude-file | 指定一个基准堆转储 |
-debug int | 设置 debug 级别 |
-version | 启动后显示版本信息就退出 |
-J <flag> | 传入启动参数,比如-J-Xmx512m |
示例
在 jmap 的示例中,我们在 D 盘生成了 5 个 dump 文件,接下来我们直接在命令行使用 jhat 命令,打开 dump 文件。
sh
jhat d:\3.hprof
打开浏览器访问 http://localhost:7000/
。
Execute Object Query Language (OQL) query
可以利用 OQL 查询想要的类,如
sql
select s from java.lang.String s where s.value.length > 100
jstack:打印 JVM 中线程快照
官方帮助文档:
https://docs.oracle.com/en/java/javase/11/tools/jstack.html
jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆栈情况。
在 thread dump 中,要留意下面几种状态
- 死锁,Deadlock(重点关注)
- 等待资源,Waiting on condition(重点关注)
- 等待获取监视器,Waiting on monitor entry(重点关注)
- 阻塞,Blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
- 对象等待中,
Object.wait()
或TIMED_WAITING
- 停止,Parked
option 参数 | 作用 |
---|---|
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用本地方法的话,可以显示 C/C++ 的堆栈 |
代码示例(死锁)
java
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();//追踪当前进程中的所有的线程
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for(Map.Entry<Thread, StackTraceElement[]> en : entries){
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
System.out.println("【Thread name is :" + t.getName() + "】");
for(StackTraceElement s : v){
System.out.println("\t" + s.toString());
}
}
}
}).start();
}
}
运行后,在命令行使用该命令:
sh
D:\>jps
8912
22164 ThreadDeadLock
18424 Jps
5084 Launcher
D:\>jstack 22164
输出的代码:
java
2022-01-31 21:51:08
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):
"DestroyJavaVM" #15 prio=5 os_prio=0 tid=0x0000000002b12800 nid=0x5b50 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x000000001e041800 nid=0x9bc waiting for monitor entry [0x000000001fd1f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.xx.jstack.ThreadDeadLock$2.run(ThreadDeadLock.java:63)
- waiting to lock <0x000000076ba1e2f0> (a java.lang.StringBuilder)
- locked <0x000000076ba1e338> (a java.lang.StringBuilder)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001e03b800 nid=0x52f8 waiting for monitor entry [0x000000001fc1f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.xx.jstack.ThreadDeadLock$1.run(ThreadDeadLock.java:35)
- waiting to lock <0x000000076ba1e338> (a java.lang.StringBuilder)
- locked <0x000000076ba1e2f0> (a java.lang.StringBuilder)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001df8b000 nid=0x3408 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001df47000 nid=0x533c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001df44800 nid=0x4ef8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001df42800 nid=0x22b8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001df40000 nid=0x3494 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001df35800 nid=0x2da8 runnable [0x000000001f4fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076b904d88> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076b904d88> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:47)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001de9e000 nid=0xac waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001def2000 nid=0x2908 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001c7c3800 nid=0x1610 in Object.wait() [0x000000001f1df000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b788ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076b788ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001de83000 nid=0x31e4 in Object.wait() [0x000000001f0de000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b786c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076b786c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000000001de62800 nid=0x575c runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002b28800 nid=0x1768 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002b2a000 nid=0x97c runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002b2c000 nid=0x4364 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002b2d800 nid=0x4608 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002b2f800 nid=0x4f38 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002b32000 nid=0xb80 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002b35000 nid=0xce4 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002b36000 nid=0x5510 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002b37800 nid=0x193c runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002b38800 nid=0x1010 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001e008000 nid=0x1144 waiting on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000001e044d58 (object 0x000000076ba1e2f0, a java.lang.StringBuilder),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000001c7c2dc8 (object 0x000000076ba1e338, a java.lang.StringBuilder),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.xx.jstack.ThreadDeadLock$2.run(ThreadDeadLock.java:63)
- waiting to lock <0x000000076ba1e2f0> (a java.lang.StringBuilder)
- locked <0x000000076ba1e338> (a java.lang.StringBuilder)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.xx.jstack.ThreadDeadLock$1.run(ThreadDeadLock.java:35)
- waiting to lock <0x000000076ba1e338> (a java.lang.StringBuilder)
- locked <0x000000076ba1e2f0> (a java.lang.StringBuilder)
Found 1 deadlock.
部分图:(可以看出 BLOCKED 进入死锁阻塞)
其他代码示例:
因为内容结果太长,所以只给代码,在命令行的输入可以自行练习查看(也可以使用 option 参数查看额外内容)。
线程睡眠代码:
java
public class TreadSleepTest {
public static void main(String[] args) {
System.out.println("hello - 1");
try {
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello - 2");
}
}
线程同步代码:
java
public class ThreadSyncTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
if (number <= 100) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
}
}
}
}
在控制台输出结果的代码:
java
public class AllStackTrace {
public static void main(String[] args) {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for(Map.Entry<Thread, StackTraceElement[]> en : entries){
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
System.out.println("【Thread name is :" + t.getName() + "】");
for(StackTraceElement s : v){
System.out.println("\t" + s.toString());
}
}
}
}
jcmd:多功能命令行
官方帮助文档:
https://docs.oracle.com/en/java/javase/11/tools/jcmd.html
在 JDK 1.7 以后,新增了一个命令行工具 jcmd。它是一个多功能的工具,可以用来实现前面除了 jstat 之外所有命令的功能。比如:用它来导出堆、内存使用、查看 Java 进程、导出线程信息、执行 GC、JVM 运行时间等。
jcmd 拥有 jmap 的大部分功能,并且在 Oracle 的官方网站上也推荐使用 jcmd 命令代 jmap 命令。
常用命令
jcmd -l:列出所有的 JVM 进程,可以替换 jps -m
。如图:
jcmd 进程号 help:针对指定的进程,列出支持的所有具体命令,如图:
jcmd 进程号 具体命令:显示指定进程的指令命令的数据。
其他命令(可以替换上面学的命令)
Thread.print
可以替换 jstack 指令GC.class_histogram
可以替换 jmap 中的-histo
操作GC.heap_dump
可以替换 jmap 中的-dump
操作GC.run
可以查看 GC 的执行情况VM.uptime
可以查看程序的总执行时间,可以替换 jstat 指令中的-t
操作VM.system_properties
可以替换jinfo -sysprops 进程 id
VM.flags
可以获取 JVM 的配置参数信息
替换 jmap 中的 -histo
操作示例(两者结果一样):
sh
jcmd 10561 GC.class_histogram
替换 jmap 中的 -dump
操作,即该命令也会生成一个 dump 文件:
sh
jcmd 10561 GC.heap_dump d:\m.hprof
jstatd:远程主机信息收集
之前的指令只涉及到监控本机的 Java 应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如 jps、jstat)。为了启用远程监控,则需要配合使用 jstatd 工具。命令 jstatd 是一个 RMI 服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd 服务器将本机的 Java 应用程序信息传递到远程计算机。