遵义市住房和城乡建设局网站wordpress 列表封面
性能问题是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。
工欲善其事,必先利其器,想要解决性能相关问题,必须要有比较好的性能诊断工具。Java作为最流行的编程语言之一,应用的性能诊断一直受到业界广泛关注。
造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。
想要定位这些问题,一款优秀的性能诊断工具必不可少,就好比中医、西医看病,中医讲究的是望、闻、问、切,西医则是借助各种检查仪器。
(一)概述
JDK本身已经集成了很多诊断工具。在大家刚接触Java学习的时候,最先了解的两个命令就是javac和java,但是除此之外,还有一些其他工具可以使用,可是并非所有的程序员都了解其他命令行程序的作用,接下来我们一起看看其他命令行程序的作用。
进入到安装JDK的bin目录,会发现还有一系列辅助工具。这些辅助工具用来获取目标JVM不同方面、不同层次的信息,帮助开发人员很好地解决Java应用程序的一些疑难杂症。
Windows系统bin目录的内容如图所示:

虽然在Windows系统下都是exe格式的可执行文件。
但事实上,它们只是Java程序的一层包装,其真正实现是在tools.jar中,如图所示。

以jps工具为例,在控制台执行jps命令和执行java -classpath %Java_HOME%/lib/tools.jar sun.tools.jps.Jps命令是等价的,即jps.exe只是这个命令的一层包装。下面介绍一些常用的命令工具。
(二)jps:查看正在运行的Java进程
jps(JVM Process Status Tool)命令用于查看系统内所有的JVM进程,可根据参数选项指定是否显示JVM的执行主类[包含main()方法的类],以及进程的本地JVMID(Local Virtual Machine Identifier),对于本地JVM进程来说,进程的本地JVMID与操作系统的进程ID是一致的。
简单来说,就是Java提供的一个显示当前所有Java进程pid的命令,和Linux系统里的ps命令很相似,ps命令主要是用来显示当前系统的进程情况,比如查看进程列表和进程ID。
在日常工作中,此命令也是最常用的命令之一。
jps的基本使用语法如下。
jps [ options ] [ hostid ]
1. [ options ]选项说明
jps工具[options]主要选项如表所示。

2. [hostid]说明
hostid表示目标主机的主机名或IP地址,如果省略该参数,则目标主机为本地主机。如果想要远程监控主机上的Java程序,需要安装jstatd。
对于网络安全要求非常严格的场所,需要自定义策略文件来满足对特定的主机或网络的访问,但是这种技术容易受到IP地址欺诈攻击。
如果由于安全问题无法通过定制的策略文件处理,那么最安全的操作是在主机本地使用jstat和jps工具。
3.使用案例
一般来说,java开发的war包或jar包都依赖tomcat来运行。
在Linux上启动Tomcat,然后在Linux上面使用ps命令查看Tomcat进程ID使用,如下所示:
ps -ef | grep "tomcat"

可以查看到Tomcat进程ID也是3460,与ps命令一致。
jps
(1)-q选项:只输出进程ID,省略主类名称。
jps -q
这个方法可以用于写shell脚本时,关闭指定的java进程。

(2)-m选项:输出JVM进程启动时传递给主类main()方法的参数。
jps -m
这个方法可以查看JVM启动时指定的运行端口。

(3)-v选项:查看输出JVM进程启动时的JVM参数。
jps -v
这个经常命令用于查看JVM启动配置。

(二)jstat:查看JVM统计信息
jstat(JVM Statistics Monitoring Tool)用于收集JVM各方面的运行数据,显示本地或远程JVM进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有图形用户界面时,只提供了纯文本控制台环境的服务器上,它是运行期定位JVM性能问题的首选工具。
常用于检测垃圾回收问题以及内存泄漏问题。它的功能非常强大,可以通过它查看堆信息的详细情况。
jstat的基本使用语法如下:
jstat -<option>[-t] [-h<lines>] <vmid>[<interval>[<count>]]
使用下面的命令可以查看jstat相关参数。
jstat -h 或 jstat -help
1.[options]选项说明
jstat工具[options]主要选项如表所示。
| 选项 | 作用 |
|---|---|
| -class | 监视类装载、卸载数量、总空间以及类装载所耗费的时间 |
| -gc | 监视Java堆状况,包括Eden区、两个survivor区、老年代、方法区等的容量、已用空间、GC时间合计等信息 |
| -gccapacity | 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间 |
| -gcutil | 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 |
| -gccauses | 与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因 |
| -gcnew | 监视新生代GC状况 |
| -gcnewcapacity | 监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 |
| -gcold | 监视老年代GC状况 |
| -gcoldcapacity | 监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 |
| -gcpermcapacity | 输出永久代使用到的最大、最小空间(JDK8之前的版本) |
| -compiler | 输出JIT编译器编译过的方法、耗时等信息 |
| -printcompilation | 输出已经被JIT编译的方法 |
2.[-t]参数说明
[-t]参数可以在输出信息前加上一个Timestamp列,显示程序的运行时间。可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。
如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
3.[-h<lines>]参数说明
[-h]参数可以在周期性数据输岀时,输出设定的行数的数据后输出一个表头信息。
4.[interval]参数说明
[interval]参数用于指定输出统计数据的周期,单位为毫秒,简单来说就是查询间隔时间。
5.[count]参数说明
[count]用于指定查询的总次数。
6.使用案例
由于jstat参数选项比较多,这里只列举一个启动了Tomcat的Linux服务器案例,查看其监视状态,命令如下所示。
# 查看进程ID
jps# 查看进程GC信息
jstat -gc 3460# 每隔1s打印一次进程信息 打印10次 并且加上时间戳
jstat -gc -t 3460 1000 10# 每隔1s打印一次进程信息 打印10次 并且加上时间戳 且每5行输出一次表头信息
jstat -gc -t -h5 3460 1000 10

运行结果的各列表示的含义如表所示。
| 参数 | 含义 |
|---|---|
| S0C | 新生代中第一个 survivor(幸存区)的容量(字节) |
| S1C | 新生代中第二个 survivor(幸存区)的容量(字节) |
| S0U | 新生代中第一个 survivor(幸存区)目前已使用空间(字节) |
| S1U | 新生代中第二个 survivor(幸存区)目前已使用空间(字节) |
| EC | 新生代中 Eden(伊甸园)的容量(字节) |
| EU | 新生代中 Eden(伊甸园)目前已使用空间(字节) |
| OC | 老年代的容量(字节) |
| OU | 老年代目前已使用空间(字节) |
| MC | MetaSpace 区的容量 |
| MU | MetaSpace 区目前已经使用的空间 |
| CCSC | 压缩类空间总容量 |
| CCSU | 压缩类空间已经使用的空间 |
| YGC | 从应用程序启动到采样时 YoungGC 的次数 |
| YGCT | 从应用程序启动到采样时 YoungGC 消耗的时间(秒) |
| FGC | 从应用程序启动到采样时 Full GC 的次数 |
| FGCT | 从应用程序启动到采样时 Full GC 的消耗的时间(秒) |
| GCT | 从应用程序启动到采样时 GC 用的总时间(秒) |
jstat还可以用来判断是否出现内存泄漏,步骤如下。
- (1)在长时间运行的Java程序中,可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。
- (2)每隔一段较长的时间重复一次上述操作,获得多组OU最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。
一个内存泄漏的例子:
public class MemoryLeakExample {private static List<String> cache = new ArrayList<>();public void addToCache() {while (true) {cache.add("Leaking string " + System.nanoTime());try {Thread.sleep(1);} catch (InterruptedException e) {break;}}}
}
该代码的cache 是 static 的,永远不会被释放。字符串不断加入,堆内存持续增长,最终抛出 OutOfMemoryError,程序终止。
这会导致刚开始一切正常,几个月后突然 OOM,难以排查。
(三)jinfo:实时查看和修改JVM配置参数
jinfo(Configuration Info for Java)可用于查看和调整JVM的配置参数。
在很多情况下,Java应用程序不会指定所有的JVM参数。
而此时,开发人员可能不知道某一个具体的JVM参数的默认值。
在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到JVM参数的当前值。
上面讲解的jps -v命令虽然可以查看JVM启动时显示指定的参数列表,但是如果想要知道未被显示指定的参数的系统默认值,就需要用到jinfo工具了。
第二个作用就是在程序运行时修改部分参数,并使之立即生效。并非所有参数都支持动态修改,只有被标记为manageable的参数可以被实时修改。
其实,这个修改能力是极其有限的,使用下面的命令查看被标记为manageable的参数。
java -XX:+PrintFlagsFinal -version | grep manageable

jinfo的基本使用语法如下:
jinfo [ options ] pid
1. [ options ]选项说明jinfo工具options主要选项如表所示。

根据进程ID查询全部参数和系统属性。
# 查看全部参数和系统属性
jinfo 4571
示例结果如下所示
Attaching to process ID 4571, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.412-b08
Java System Properties:java.vendor = Red Hat, Inc.
sun.java.launcher = SUN_STANDARD
catalina.base = /usr/local/tomcat
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
catalina.useNaming = true
os.name = Linux
sun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/classes
java.util.logging.config.file = /usr/local/tomcat/conf/logging.properties
org.apache.el.GET_CLASSLOADER_USE_PRIVILEGED = false
java.vm.specification.vendor = Oracle Corporation
java.runtime.version = 1.8.0_412-b08
user.name = www
tomcat.util.scan.StandardJarScanFilter.jarsToScan = log4j-taglib*.jar,log4j-web*.jar,log4javascript*.jar,slf4j-taglib*.jar
shared.loader =
tomcat.util.buf.StringCache.byte.enabled = true
user.language = en
java.naming.factory.initial = org.apache.naming.java.javaURLContextFactory
sun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/amd64
jdk.tls.ephemeralDHKeySize = 2048
java.version = 1.8.0_412
java.util.logging.manager = org.apache.juli.ClassLoaderLogManager
user.timezone = Asia/Shanghai
sun.arch.data.model = 64
java.util.concurrent.ForkJoinPool.common.threadFactory = org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory
java.endorsed.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/endorsed
sun.cpu.isalist =
sun.jnu.encoding = UTF-8
file.encoding.pkg = sun.io
package.access = sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.
file.separator = /
java.specification.name = Java Platform API Specification
java.class.version = 52.0
user.country = US
java.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre
java.vm.info = mixed mode
os.version = 3.10.0-957.el7.x86_64
path.separator = :
java.vm.version = 25.412-b08
java.protocol.handler.pkgs = org.apache.catalina.webresources
java.awt.printerjob = sun.print.PSPrinterJob
sun.io.unicode.encoding = UnicodeLittle
java.specification.maintenance.version = 5
awt.toolkit = sun.awt.X11.XToolkit
package.definition = sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.
java.naming.factory.url.pkgs = org.apache.naming
java.security.egd = file:/dev/./urandom
user.home = /home/www
org.apache.catalina.security.SecurityListener.UMASK = 0027
java.specification.vendor = Oracle Corporation
tomcat.util.scan.StandardJarScanFilter.jarsToSkip = annotations-api.jar,ant-junit*.jar,ant-launcher*.jar,ant*.jar,asm-*.jar,aspectj*.jar,bcel*.jar,biz.aQute.bnd*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-ssi.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina.jar,cglib-*.jar,cobertura-*.jar,commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,commons-compress*.jar,commons-daemon.jar,commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,commons-math*.jar,commons-pool*.jar,derby-*.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,geronimo-spec-jaxrpc*.jar,h2*.jar,ha-api-*.jar,hamcrest-*.jar,hibernate*.jar,httpclient*.jar,icu4j-*.jar,jasper-el.jar,jasper.jar,jaspic-api.jar,jaxb-*.jar,jaxen-*.jar,jaxws-rt-*.jar,jdom-*.jar,jetty-*.jar,jmx-tools.jar,jmx.jar,jsp-api.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,log4j*.jar,mail*.jar,objenesis-*.jar,oraclepki.jar,org.hamcrest.core_*.jar,org.junit_*.jar,oro-*.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,unboundid-ldapsdk-*.jar,websocket-api.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar
java.library.path = /usr/local/apr/lib
java.vendor.url = https://www.redhat.com/
java.vm.vendor = Red Hat, Inc.
common.loader = "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
java.runtime.name = OpenJDK Runtime Environment
sun.java.command = org.apache.catalina.startup.Bootstrap start
java.class.path = /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
java.vm.specification.name = Java Virtual Machine Specification
java.vm.specification.version = 1.8
catalina.home = /usr/local/tomcat
sun.cpu.endian = little
sun.os.patch.level = unknown
java.io.tmpdir = /usr/local/tomcat/temp
java.vendor.url.bug = https://access.redhat.com/support/cases/
server.loader =
os.arch = amd64
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
java.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jre/lib/ext:/usr/java/packages/lib/ext
user.dir = /
line.separator = java.vm.name = OpenJDK 64-Bit Server VM
ignore.endorsed.dirs =
file.encoding = UTF-8
java.specification.version = 1.8VM Flags:
Non-default VM flags: -XX:CICompilerCount=12 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=8327790592 -XX:MaxNewSize=2775580672 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
Command line: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.security.egd=file:/dev/./urandom -Xms256m -Xmx7941m -Dfile.encoding=UTF-8 -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Djava.library.path=/usr/local/apr/lib -Dignore.endorsed.dirs= -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp
(五)jmap:导出内存映像文件和内存使用情况
jmap(JVM Memory Map)用于生成JVM的内存转储快照,生成heapdump文件且可以查询finalize执行队列,以及Java堆与元空间的一些信息。
jmap的作用并不仅仅是为了获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
开发人员可以在控制台中输入命令“jmap-help”,查阅jmap工具的具体使用方式和标准选项配置。jmap的基本使用语法如下。
jmap [option] <pid>
jmap [option] <executable <core>
jmap [option] [server_id@]<remote server IP or hostname>
1.[ options ]选项说明
jmap工具[options]主要选项如表所示。

# 示例 1: 使用 jmap 获取指定进程的堆内存信息
jmap -heap <pid># 示例 2: 使用 jmap 获取指定进程的类统计信息
jmap -histo <pid># 示例 3: 使用 jmap 将指定进程的堆内存转储到文件中
jmap -dump:format=b,file=heapdump.hprof <pid># 示例 4: 使用 jmap 获取远程服务器上指定进程的堆内存信息
jmap -heap [server_id@]<remote server IP or hostname>:<pid># 示例 5: 使用 jmap 分析 core 文件
jmap -histo <executable> <core>
这些参数和Linux下输入显示的命令多少会有一些不同,也受JDK版本的影响。其中选项-dump、-heap、-histo是开发人员在工作中使用频率较高的指令。
2. 使用案例
(1)-dump选项:导出内存映像文件。
一般来说,使用jmap指令生成dump文件的操作算得上是最常用的jmap命令之一,将堆中所有存活对象导出至一个文件之中。
执行该命令,JVM会将整个Java堆二进制格式转储到指定filename的文件中。
live子选项是可选的,如果指定了live子选项,堆中只有存活的对象会被转储。通常在写dump文件前会触发一次Full GC,所以dump文件里保存的都是Full GC后留下的对象信息。
由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件需要耗费更长的时间来完成。
如果想要浏览dump文件,读者可以使用jhat(Java堆分析工具)读取生成的文件,也可以使用可视化工具进行解读,比如MAT内存分析工具。
获取dump文件有手动获取和自动获取两种方式。
手动获取的意思是当发现系统需要优化或者需要解决内存问题时,需要开发者主动执行jmap命令,导出dump文件,手动获取命令如下。
#手动获取堆内存全部信息
jmap -dump:format=b,file=<filename.hprof> <pid>
#手动获取堆内存存活对象全部信息
jmap -dump:live,format=b,file=<filename.hprof> <pid>
代码演示:
import java.util.ArrayList;/*** -Xms60m -Xmx60m -XX:SurvivorRatio=8*/
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]; // 100KBlist.add(arr);try {Thread.sleep(60);} catch (InterruptedException e) {e.printStackTrace();}}}
}
javac GCTest.java
java -Xms60m -Xmx60m -XX:SurvivorRatio=8 GCTest
将代码编译运行。
jmap -dump:format=b,file=D:/dump1.hprof 47948
运行程序后通过jps查看pid进程,最终该程序会产生内存溢出,在问题出现之前使用上述命令导出dump文件即可。

当程序发生内存溢出退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时。
若能在OOM时,自动导出dump文件就显得非常迫切。可以配置JVM参数“-XX:+HeapDumpOnOutOfMemoryError:”使程序发生OOM时,导出应用程序的当前堆快照。
-Xms60m
-Xmx60m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:/autoDump.hprof
java -Xms60m -Xmx60m -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/autoDump.hprof GCTest
(2)-heap选项:显示堆内存相关信息。
命令如下。
#50380表示当前进程ID
jmap -heap 51376
输出运行结果示例如下:
C:\Users\Administrator>jmap -heap 51376
Attaching to process ID 51376, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.275-b01using thread-local object allocation.
Parallel GC with 10 thread(s)Heap Configuration:MinHeapFreeRatio = 0MaxHeapFreeRatio = 100MaxHeapSize = 62914560 (60.0MB)NewSize = 20971520 (20.0MB)MaxNewSize = 20971520 (20.0MB)OldSize = 41943040 (40.0MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 0 (0.0MB)Heap Usage:
PS Young Generation
Eden Space:capacity = 16777216 (16.0MB)used = 10362656 (9.882598876953125MB)free = 6414560 (6.117401123046875MB)61.76624298095703% used
From Space:capacity = 2097152 (2.0MB)used = 0 (0.0MB)free = 2097152 (2.0MB)0.0% used
To Space:capacity = 2097152 (2.0MB)used = 0 (0.0MB)free = 2097152 (2.0MB)0.0% used
PS Old Generationcapacity = 41943040 (40.0MB)used = 41853832 (39.91492462158203MB)free = 89208 (0.08507537841796875MB)99.78731155395508% used
打印heap的概要信息、GC使用的算法、heap的配置和使用情况,可以判断当前堆内存使用情况以及垃圾回收情况。
该 Java 应用几乎耗尽了所有内存,老年代(Old Generation)已被填满 99.78%,应用处于严重的内存不足状态,随时可能因无法分配新对象而触发 Full GC,甚至抛出 OutOfMemoryError 导致进程崩溃。
(3)-hiso选项:显示堆中对象的统计信息。
命令如下:
#1520表示当前进程ID
jmap -histo 1520
输出示例如下:
num #instances #bytes class name
----------------------------------------------1: 805 30382712 [B2: 272 769168 [I3: 5066 610672 [C4: 4358 104592 java.lang.String5: 595 68160 java.lang.Class6: 1191 57744 [Ljava.lang.Object;7: 792 31680 java.util.TreeMap$Entry8: 622 24880 java.util.LinkedHashMap$Entry9: 429 22352 [Ljava.lang.String;10: 29 10608 [Ljava.util.HashMap$Node;11: 330 10560 java.util.HashMap$Node12: 229 7328 java.util.Hashtable$Entry13: 75 5400 java.lang.reflect.Field14: 107 4280 java.lang.ref.SoftReference15: 256 4096 java.lang.Integer16: 130 3120 java.lang.StringBuffer17: 8 3008 java.lang.Thread18: 43 2752 java.net.URL19: 114 2736 java.lang.StringBuilder20: 15 2624 [Ljava.util.Hashtable$Entry;21: 5 2600 [J22: 2 2392 [[Ljava.lang.Object;23: 28 2240 [S24: 70 2240 java.util.concurrent.ConcurrentHashMap$Node25: 2 2080 [[C26: 60 1920 java.io.File27: 39 1872 sun.util.locale.LocaleObjectCache$CacheEntry28: 16 1664 [Ljava.util.concurrent.ConcurrentHashMap$Node;29: 1 1568 [[B30: 16 1280 java.lang.reflect.Constructor31: 20 1280 java.util.concurrent.ConcurrentHashMap32: 2 1064 [Ljava.lang.invoke.MethodHandle;33: 19 1064 sun.misc.URLClassPath$JarLoader34: 1 1040 [Ljava.lang.Integer;35: 26 1040 java.io.ObjectStreamField36: 20 960 java.util.HashMap37: 16 896 java.lang.Class$ReflectionData38: 21 840 sun.util.locale.BaseLocale$Key39: 33 792 java.io.ExpiringCache$Entry40: 45 720 java.lang.Object41: 19 608 java.util.Locale42: 19 608 sun.util.locale.BaseLocale43: 21 504 java.util.Locale$LocaleKey44: 12 480 java.security.AccessControlContext45: 7 424 [Ljava.lang.reflect.Field;46: 17 408 sun.misc.MetaIndex47: 1 384 java.lang.ref.Finalizer$FinalizerThread48: 16 384 java.net.Parts49: 6 384 java.nio.DirectByteBuffer50: 1 376 java.lang.ref.Reference$ReferenceHandler51: 6 336 java.nio.DirectLongBufferU52: 17 320 [Ljava.lang.Class;53: 10 320 java.lang.OutOfMemoryError54: 8 320 java.lang.ref.Finalizer55: 10 320 java.lang.ref.ReferenceQueue56: 10 288 [Ljava.io.ObjectStreamField;57: 12 288 java.util.ArrayList58: 6 288 java.util.Hashtable59: 7 280 java.io.FileDescriptor60: 5 280 java.util.ResourceBundle$CacheKey61: 5 280 sun.util.calendar.ZoneInfo62: 8 256 java.util.Vector63: 4 256 sun.nio.cs.ext.DoubleByte$Encoder64: 3 240 [Ljava.util.WeakHashMap$Entry;65: 2 240 [[Ljava.lang.String;66: 5 240 java.nio.HeapByteBuffer67: 4 224 java.util.LinkedHashMap68: 4 224 sun.nio.cs.ext.DoubleByte$Decoder69: 5 200 java.util.WeakHashMap$Entry70: 8 192 [Ljava.lang.reflect.Constructor;71: 12 192 java.lang.ref.ReferenceQueue$Lock72: 4 192 java.nio.HeapCharBuffer73: 4 192 java.util.TreeMap74: 8 192 sun.misc.URLClassPath$375: 8 192 sun.reflect.NativeConstructorAccessorImpl76: 2 160 [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;77: 5 160 java.io.FileInputStream78: 4 160 java.security.PrivilegedActionException79: 4 160 java.security.ProtectionDomain80: 5 160 java.util.ResourceBundle$LoaderReference81: 5 160 sun.util.locale.provider.LocaleProviderAdapter$Type82: 3 144 java.util.Hashtable$Enumerator83: 3 144 java.util.Properties84: 3 144 java.util.WeakHashMap85: 6 144 sun.misc.PerfCounter86: 3 144 sun.misc.URLClassPath87: 3 144 sun.nio.cs.StreamEncoder88: 6 144 sun.security.action.GetPropertyAction89: 2 128 java.io.ExpiringCache$190: 4 128 java.lang.ThreadLocal$ThreadLocalMap$Entry91: 4 128 java.lang.ref.WeakReference92: 4 128 java.security.CodeSource93: 4 128 java.util.Stack94: 8 128 sun.reflect.DelegatingConstructorAccessorImpl95: 3 120 java.io.BufferedWriter96: 5 120 java.util.Collections$UnmodifiableRandomAccessList97: 5 120 sun.nio.fs.WindowsPathType98: 2 96 java.lang.ThreadGroup99: 2 96 java.util.ResourceBundle$BundleReference100: 1 96 sun.misc.Launcher$AppClassLoader101: 1 96 sun.util.calendar.Gregorian$Date102: 1 88 java.lang.reflect.Method103: 1 88 sun.misc.Launcher$ExtClassLoader104: 2 80 [Lsun.util.locale.provider.LocaleProviderAdapter$Type;105: 2 80 java.io.BufferedInputStream106: 2 80 java.io.ExpiringCache107: 2 80 java.util.ServiceLoader$LazyIterator108: 2 80 sun.misc.URLClassPath$1109: 2 80 sun.misc.URLClassPath$2110: 2 80 sun.util.locale.LanguageTag111: 3 72 java.io.OutputStreamWriter112: 3 72 java.lang.Class$1113: 3 72 java.lang.RuntimePermission114: 3 72 java.util.Arrays$ArrayList115: 3 72 java.util.LinkedList$Node116: 1 72 java.util.ResourceBundle$RBClassLoader117: 3 72 java.util.concurrent.atomic.AtomicLong118: 1 72 sun.util.locale.provider.JRELocaleProviderAdapter119: 2 64 [Ljava.lang.Thread;120: 4 64 [Ljava.security.Principal;121: 2 64 java.io.DataInputStream122: 2 64 java.io.FileNotFoundException123: 2 64 java.io.FileOutputStream124: 2 64 java.io.PrintStream125: 2 64 java.lang.ClassValue$Entry126: 2 64 java.lang.StringCoding$StringDecoder127: 2 64 java.lang.StringCoding$StringEncoder128: 2 64 java.lang.VirtualMachineError129: 2 64 java.lang.ref.ReferenceQueue$Null130: 4 64 java.security.ProtectionDomain$Key131: 2 64 java.util.ArrayList$Itr132: 2 64 java.util.LinkedHashMap$LinkedEntryIterator133: 2 64 java.util.ServiceLoader134: 1 56 sun.nio.cs.ISO_8859_1$Encoder135: 1 48 [Ljava.io.File;136: 1 48 [Ljava.net.URL;137: 2 48 [Ljava.util.Enumeration;138: 2 48 java.io.BufferedOutputStream139: 1 48 java.io.BufferedReader140: 2 48 java.io.File$PathStatus141: 3 48 java.lang.ThreadLocal142: 2 48 java.lang.ThreadLocal$ThreadLocalMap143: 2 48 java.net.URLClassLoader$3144: 2 48 java.nio.charset.CoderResult145: 3 48 java.nio.charset.CodingErrorAction146: 2 48 java.util.Collections$SynchronizedSet147: 3 48 java.util.HashSet148: 2 48 java.util.ServiceLoader$1149: 1 48 java.util.StringTokenizer150: 2 48 sun.misc.CompoundEnumeration151: 2 48 sun.misc.FileURLMapper152: 2 48 sun.misc.NativeSignalHandler153: 2 48 sun.misc.Signal154: 3 48 sun.net.www.protocol.jar.Handler155: 1 48 sun.nio.cs.StreamDecoder156: 1 48 sun.util.locale.provider.LocaleResources$ResourceReference157: 1 48 sun.util.resources.TimeZoneNames158: 1 48 sun.util.resources.en.TimeZoneNames_en159: 1 40 [Lsun.nio.fs.WindowsPathType;160: 1 40 java.lang.ClassLoader$NativeLibrary161: 1 40 java.util.ResourceBundle$1162: 1 40 sun.nio.cs.StandardCharsets$Aliases163: 1 40 sun.nio.cs.StandardCharsets$Cache164: 1 40 sun.nio.cs.StandardCharsets$Classes165: 1 40 sun.nio.cs.ext.ExtendedCharsets166: 1 32 [Ljava.lang.OutOfMemoryError;167: 2 32 [Ljava.lang.StackTraceElement;168: 1 32 [Ljava.lang.ThreadGroup;169: 1 32 java.io.ByteArrayInputStream170: 2 32 java.io.FileInputStream$1171: 1 32 java.io.FilePermission172: 1 32 java.io.WinNTFileSystem173: 1 32 java.lang.ArithmeticException174: 2 32 java.lang.Boolean175: 2 32 java.lang.ClassLoader$2176: 1 32 java.lang.NullPointerException177: 2 32 java.net.URLClassLoader$3$1178: 2 32 java.nio.ByteOrder179: 1 32 java.security.BasicPermissionCollection180: 1 32 java.security.Permissions181: 2 32 java.util.LinkedHashMap$LinkedEntrySet182: 2 32 java.util.LinkedHashMap$LinkedKeySet183: 1 32 java.util.LinkedList184: 2 32 java.util.concurrent.atomic.AtomicInteger185: 1 32 java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl186: 2 32 sun.misc.URLClassPath$JarLoader$1187: 1 32 sun.nio.cs.StandardCharsets188: 1 32 sun.nio.fs.WindowsFileSystem189: 1 32 sun.util.locale.provider.LocaleResources190: 1 32 sun.util.locale.provider.LocaleServiceProviderPool191: 1 24 [Ljava.io.File$PathStatus;192: 1 24 [Ljava.lang.ClassValue$Entry;193: 1 24 [Ljava.lang.reflect.Method;194: 1 24 [Lsun.launcher.LauncherHelper;195: 1 24 java.io.ByteArrayOutputStream196: 1 24 java.io.FilePermissionCollection197: 1 24 java.io.FileReader198: 1 24 java.lang.ClassValue$Version199: 1 24 java.lang.invoke.MethodHandleImpl$4200: 1 24 java.lang.reflect.ReflectPermission201: 1 24 java.util.BitSet202: 1 24 java.util.Collections$EmptyMap203: 1 24 java.util.Collections$SetFromMap204: 1 24 java.util.Collections$UnmodifiableCollection$1205: 1 24 java.util.Date206: 1 24 java.util.Locale$Cache207: 1 24 java.util.ResourceBundle$Control$CandidateListCache208: 1 24 sun.launcher.LauncherHelper209: 1 24 sun.misc.URLClassPath$FileLoader210: 1 24 sun.nio.cs.ISO_8859_1211: 1 24 sun.nio.cs.Surrogate$Parser212: 1 24 sun.nio.cs.ext.GBK213: 1 24 sun.nio.cs.ext.MS936214: 1 24 sun.util.locale.BaseLocale$Cache215: 1 24 sun.util.locale.provider.SPILocaleProviderAdapter$1216: 1 24 sun.util.locale.provider.TimeZoneNameProviderImpl217: 1 24 sun.util.resources.LocaleData$1218: 1 16 [Ljava.lang.Throwable;219: 1 16 [Ljava.security.cert.Certificate;220: 1 16 [Lsun.util.calendar.ZoneInfoFile$ZoneOffsetTransitionRule;221: 1 16 java.io.FileDescriptor$1222: 1 16 java.lang.CharacterDataLatin1223: 1 16 java.lang.ClassValue$Identity224: 1 16 java.lang.Runtime225: 1 16 java.lang.String$CaseInsensitiveComparator226: 1 16 java.lang.System$2227: 1 16 java.lang.Terminator$1228: 1 16 java.lang.invoke.MemberName$Factory229: 1 16 java.lang.invoke.MethodHandleImpl$2230: 1 16 java.lang.invoke.MethodHandleImpl$3231: 1 16 java.lang.ref.Reference$1232: 1 16 java.lang.ref.Reference$Lock233: 1 16 java.lang.reflect.ReflectAccess234: 1 16 java.net.URLClassLoader$7235: 1 16 java.nio.Bits$1236: 1 16 java.nio.charset.CoderResult$1237: 1 16 java.nio.charset.CoderResult$2238: 1 16 java.security.ProtectionDomain$2239: 1 16 java.security.ProtectionDomain$JavaSecurityAccessImpl240: 1 16 java.util.Collections$EmptyIterator241: 1 16 java.util.Collections$EmptyList242: 1 16 java.util.Collections$EmptySet243: 1 16 java.util.Collections$UnmodifiableSet244: 1 16 java.util.Hashtable$EntrySet245: 1 16 java.util.Hashtable$KeySet246: 1 16 java.util.ResourceBundle$Control247: 1 16 java.util.ResourceBundle$RBClassLoader$1248: 1 16 java.util.TimeZone$1249: 1 16 java.util.WeakHashMap$KeySet250: 1 16 java.util.zip.ZipFile$1251: 1 16 sun.misc.ASCIICaseInsensitiveComparator252: 1 16 sun.misc.Launcher253: 1 16 sun.misc.Launcher$BootClassPathHolder$1254: 1 16 sun.misc.Launcher$Factory255: 1 16 sun.misc.Perf256: 1 16 sun.misc.Unsafe257: 1 16 sun.net.www.protocol.file.Handler258: 1 16 sun.nio.fs.WindowsFileSystemProvider259: 1 16 sun.reflect.ReflectionFactory260: 1 16 sun.util.calendar.Gregorian261: 1 16 sun.util.calendar.ZoneInfoFile$1262: 1 16 sun.util.calendar.ZoneInfoFile$Checksum263: 1 16 sun.util.locale.provider.AuxLocaleProviderAdapter$NullProvider264: 1 16 sun.util.locale.provider.JRELocaleProviderAdapter$1265: 1 16 sun.util.locale.provider.SPILocaleProviderAdapter266: 1 16 sun.util.locale.provider.TimeZoneNameUtility$TimeZoneNameGetter267: 1 16 sun.util.resources.LocaleData268: 1 16 sun.util.resources.LocaleData$LocaleDataResourceBundleControl
Total 16534 32178440
上面结果中,instances表示当前的实例数量;bytes表示对象占用的内存大小;classs name表示类名,按照内存大小逆序排列。
排名第一的类 [B (byte数组) 有 805个实例,却占据了 30,382,712 字节(约 30.38 MB)。
这说明正在创建大量的大型byte数组,这与上面的代码 new byte[1024 * 100](每次分配100KB)完全吻合。
由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程不改变堆中数据的状态。
也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该快照的分析结果存在偏差。例如,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live选项将无法探知到这些对象。
另外如果某个线程长时间无法跑到安全点,jmap将一直等待下去。
与前面讲的jstat不同,垃圾收集器会主动将jstat所需要的摘要数据保存至固定位置中,而jstat只需要直接读取即可。
(六)jhat:JDK自带堆分析工具
jhat(JVM Heap Analysis Tool)命令一般与jmap命令搭配使用,用于分析jmap生成的dump文件(堆转储快照)。
jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果。
使用了jhat命令,就启动了一个http服务,端口是7000,即通过访问http://localhost:7000/就可以在浏览器中查看结果。
jhat命令在JDK9中已经被删除,官方建议用VisualVM代替。实际工作中一般不会直接在生产服务器使用jhat分析dump文件。
jhat的基本使用语法如下。
jhat [ options ] [ hostid ]
1.[ options ]选项说明
jhat工具[options]主要选项如表所示。

下面使用jhat命令分析jmap导出的dump文件,命令如下。
jhat d:\autoDump.hprof
当界面出现Server is ready时,就可以在浏览器访问http://localhost:7000/了,如图:

分析结果默认以包的形式分组展示,当分析内存溢出或者内存泄漏问题的时候一般会用到“Show heap histogram”功能,它的作用和jmap –histo一样,如图所示。

在这里可以找到内存中使用空间最大的对象。通常导出的堆快照信息非常大,可能很难通过页面上简单的链接索引找到想要的信息。
为此,jhat还支持使用OQL(Object Query Language)语句对堆快照进行查询。执行OQL语言的界面非常简洁。
单击“Execute Object Query Language(OQL)query”即可进入OQL查询页面,它是一种类似SQL的语法,可以对内存中的对象进行查询统计,例如代码清单,查询了内存中长度大于500的字符串。
select s from java.lang.String s where s.value.length >500

平常使用jhat命令的频率并不高,所以此处也不再过多赘述。
(七)jstack:打印JVM中线程快照
jstack(JVM Stack Trace)用于生成JVM指定进程当前时刻的线程快照(Thread Dump),方便用户跟踪JVM堆栈信息。
线程快照就是当前JVM内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用是可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题,这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。

其中线程的Deadlock、Waiting on condition、Waiting on monitor entry以及Blocked状态需要在分析线程栈的时候重点关注。
jstack [ option ] <pid>
1.使用案例
代码清单21-4演示了线程死锁,使用jstack命令观察线程状态
import java.util.ArrayList;public class ThreadDeadlock {public static void main(String[] args) {StringBuilder s1 = new StringBuilder();StringBuilder s2 = new StringBuilder();new Thread() {@Overridepublic 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() {@Overridepublic 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();}}
}
上面例子很简单,启动了两个线程,分别获取对方的资源,如此造成死锁。下面启动程序,使用jstack命令查看线程状态。
输出结果如下:
C:\Users\Administrator>jstack 14360
2025-09-16 11:51:51
Full thread dump OpenJDK 64-Bit Server VM (25.275-b01 mixed mode):"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000027731745000 nid=0xba00 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Thread-1" #13 prio=5 os_prio=0 tid=0x000002773159e800 nid=0x4f50 waiting for monitor entry [0x000000a7144ff000]java.lang.Thread.State: BLOCKED (on object monitor)at ThreadDeadlock$2.run(ThreadDeadlock.java:41)- waiting to lock <0x000000071939b450> (a java.lang.StringBuilder)- locked <0x000000071939b498> (a java.lang.StringBuilder)at java.lang.Thread.run(Thread.java:748)"Thread-0" #12 prio=5 os_prio=0 tid=0x0000027731517000 nid=0xbbf0 waiting for monitor entry [0x000000a7143ff000]java.lang.Thread.State: BLOCKED (on object monitor)at ThreadDeadlock$1.run(ThreadDeadlock.java:20)- waiting to lock <0x000000071939b498> (a java.lang.StringBuilder)- locked <0x000000071939b450> (a java.lang.StringBuilder)"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x00000277313e2000 nid=0xb428 runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000277313e1800 nid=0xa0a0 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000277313e0800 nid=0xad8c waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000277313e0000 nid=0xa738 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000277313de800 nid=0xbffc waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000277313db000 nid=0xb78c runnable [0x000000a713cfe000]java.lang.Thread.State: RUNNABLEat 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 <0x0000000719553e48> (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 <0x0000000719553e48> (a java.io.InputStreamReader)at java.io.BufferedReader.readLine(BufferedReader.java:389)at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:55)"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000002772dbd2800 nid=0x182c waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000002772dbde000 nid=0xc998 runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000002772dba4000 nid=0xaff0 in Object.wait() [0x000000a7139ff000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x0000000716d08ee0> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)- locked <0x0000000716d08ee0> (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=0x000002772db9a800 nid=0xc6c0 in Object.wait() [0x000000a7138fe000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x0000000716d06c00> (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x0000000716d06c00> (a java.lang.ref.Reference$Lock)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"VM Thread" os_prio=2 tid=0x00000277096ae000 nid=0xb898 runnable"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000002770960f000 nid=0x6a8c runnable"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000027709610800 nid=0xba3c runnable"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000027709615000 nid=0xc784 runnable"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000027709617000 nid=0xa9ec runnable"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000027709619800 nid=0xb468 runnable"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000002770961a800 nid=0x7af0 runnable"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000002770961e000 nid=0xc0dc runnable"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000002770961f000 nid=0xa594 runnable"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000027709620800 nid=0xa570 runnable"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000027709622000 nid=0xc12c runnable"VM Periodic Task Thread" os_prio=2 tid=0x0000027731403800 nid=0x65e8 waiting on conditionJNI global references: 12Found one Java-level deadlock:
=============================
"Thread-1":waiting to lock monitor 0x00000277319c41c8 (object 0x000000071939b450, a java.lang.StringBuilder),which is held by "Thread-0"
"Thread-0":waiting to lock monitor 0x000002772dba3b48 (object 0x000000071939b498, a java.lang.StringBuilder),which is held by "Thread-1"Java stack information for the threads listed above:
===================================================
"Thread-1":at ThreadDeadlock$2.run(ThreadDeadlock.java:41)- waiting to lock <0x000000071939b450> (a java.lang.StringBuilder)- locked <0x000000071939b498> (a java.lang.StringBuilder)at java.lang.Thread.run(Thread.java:748)
"Thread-0":at ThreadDeadlock$1.run(ThreadDeadlock.java:20)- waiting to lock <0x000000071939b498> (a java.lang.StringBuilder)- locked <0x000000071939b450> (a java.lang.StringBuilder)Found 1 deadlock.
从上面结果中可以发现,Thread-1线程和Thread-0线程互相等待对方的资源,问题代码出现“ThreadDeadLock$2.run()”行。
在死锁情况出现时,可以很方便地帮助定位到问题。也可以通过Thread.getAllStackTraces()方法获取所有线程的状态
import java.util.Map;
import java.util.Set;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());}}}
}
运行结果可以看到各个线程的状态。
【Thread name is :Monitor Ctrl-Break】java.net.Socket.setImpl(Socket.java:521)java.net.Socket.<init>(Socket.java:442)java.net.Socket.<init>(Socket.java:229)com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:52)
【Thread name is :main】java.lang.Thread.dumpThreads(Native Method)java.lang.Thread.getAllStackTraces(Thread.java:1610)AllStackTrace.main(AllStackTrace.java:6)
【Thread name is :Attach Listener】
【Thread name is :Reference Handler】java.lang.Object.wait(Native Method)java.lang.Object.wait(Object.java:502)java.lang.ref.Reference.tryHandlePending(Reference.java:191)java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
【Thread name is :Signal Dispatcher】
【Thread name is :Finalizer】java.lang.Object.wait(Native Method)java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
(1)Monitor Ctrl-Break
【Thread name is :Monitor Ctrl-Break】java.net.Socket.setImpl(Socket.java:521)java.net.Socket.<init>(Socket.java:442)java.net.Socket.<init>(Socket.java:229)com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:52)
- 作用:这是 IntelliJ IDEA(或其他 IDE)注入的一个特殊线程,用于实现 Ctrl + Break(或 Ctrl + C)中断功能。
- 它监听控制台输入,当用户按下中断键时,它可以捕获信号并触发线程 dump 或中断程序执行。
- 常见于在 IDE 中运行 Java 程序时。
- 堆栈说明:它正在使用 Socket 与 IDE 的运行时通信模块进行通信(用于传输中断信号或性能数据)。
注意:如果你在命令行运行程序,通常不会有这个线程。
(2)main
【Thread name is :main】java.lang.Thread.dumpThreads(Native Method)java.lang.Thread.getAllStackTraces(Thread.java:1610)AllStackTrace.main(AllStackTrace.java:6)
- 作用:这是 Java 程序的主执行线程,所有程序逻辑默认从这里开始。
- 在你的代码中,正是
main线程调用了Thread.getAllStackTraces()来获取所有线程的堆栈。 - 堆栈说明:
dumpThreads(Native Method):本地方法,用于获取所有线程的堆栈。getAllStackTraces():Java 层调用。AllStackTrace.main():你的代码入口。
这是你的程序逻辑运行的地方。
(3)Attach Listener
【Thread name is :Attach Listener】
- 作用:这是一个 JVM 内部线程,用于支持 动态 attach 机制。
- 允许外部工具(如
jstack,jmap,jconsole,VisualVM等)通过VirtualMachine.attach(pid)连接到正在运行的 JVM。 - 例如,当你运行
jstack 12345时,就是通过这个线程建立连接并获取线程 dump。 - 它通常在需要诊断时才被激活。
安全提示:在生产环境中,可以通过
-XX:+DisableAttachMechanism禁用此功能以提高安全性。
(4)Reference Handler
【Thread name is :Reference Handler】java.lang.Object.wait(Native Method)java.lang.Object.wait(Object.java:502)java.lang.ref.Reference.tryHandlePending(Reference.java:191)java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
- 作用:处理 Java 中的 引用对象(Reference),比如:
SoftReference(软引用)WeakReference(弱引用)PhantomReference(虚引用)
- 当这些引用指向的对象被 GC 回收后,
Reference Handler会将它们加入到对应的ReferenceQueue中,供程序后续处理。 - 是 JVM 垃圾回收机制的重要组成部分。
例如:WeakHashMap 就依赖这个线程来清理失效的条目。
(5)Finalizer
【Thread name is :Finalizer】java.lang.Object.wait(Native Method)java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
- 作用:负责调用对象的
finalize()方法(如果该类重写了此方法)。 - 当一个对象被 GC 标记为可回收,且该类有
finalize()方法时,JVM 会将其放入一个队列,由Finalizer线程异步调用其finalize()。 - 注意:
finalize()已被标记为 @Deprecated(从 Java 9 开始),不推荐使用,因为它不可控、性能差、可能导致内存泄漏。
建议使用
try-with-resources或手动清理资源,而不是依赖finalize()。
(6) Signal Dispatcher
【Thread name is :Signal Dispatcher】
- 作用:处理操作系统发送给 JVM 的 信号(Signals)。
- 例如:
SIGQUIT(Ctrl + \):触发线程 dumpSIGTERM:优雅关闭SIGINT(Ctrl + C):中断程序
- 它是一个 JVM 内部线程,负责将这些信号转发给相应的处理逻辑。
例如:你按
Ctrl + \时,Signal Dispatcher会触发打印所有线程的堆栈。
| 线程名称 | 作用 | 是否重要 |
|---|---|---|
main | 程序主执行线程 | ✅ 必须 |
Reference Handler | 处理软/弱/虚引用的回收 | ✅ 核心 GC 组件 |
Finalizer | 调用对象的 finalize() 方法 | ⚠️ 已废弃,慎用 |
Attach Listener | 支持 jstack、jmap 等工具 attach 到 JVM | ✅ 诊断必备 |
Signal Dispatcher | 处理系统信号(如 Ctrl+C) | ✅ 运行时支持 |
Monitor Ctrl-Break | IDE 注入,支持中断和监控 | 🛠️ 仅 IDE 环境存在 |
(八)jcmd:多功能命令行
在JDK1.7以后,新增了一个命令行工具jcmd。
它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能,比如用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。
jcmd拥有jmap的大部分功能,并且官方也推荐使用jcmd命令代替jmap命令。至于jstat的功能,虽然jcmd复制了jstat的部分代码,并支持通过PerfCounter.print子命令来打印所有的Performance Counter,但是它没有保留jstat的输出格式,也没有重复打印的功能。
jcmd的基本使用语法如表所示。

1. jcmd pid help选项:针对指定的进程,列出支持的所有命令
针对每一个进程,jcmd可以使用help命令列出它们所支持的命令,如下所示。
jcmd 25935 help
运行结果如下。
25935:
The following commands are available:
Compiler.CodeHeap_Analytics
Compiler.codecache
Compiler.codelist
Compiler.directives_add
Compiler.directives_clear
Compiler.directives_print
Compiler.directives_remove
Compiler.perfmap
Compiler.queue
GC.class_histogram
GC.finalizer_info
GC.heap_dump
GC.heap_info
GC.run
GC.run_finalization
JFR.check
JFR.configure
JFR.dump
JFR.start
JFR.stop
JVMTI.agent_load
JVMTI.data_dump
ManagementAgent.start
ManagementAgent.start_local
ManagementAgent.status
ManagementAgent.stop
System.trim_native_heap
Thread.print
VM.cds
VM.class_hierarchy
VM.classloader_stats
VM.classloaders
VM.command_line
VM.dynlibs
VM.events
VM.flags
VM.info
VM.log
VM.metaspace
VM.native_memory
VM.print_touched_methods
VM.set_flag
VM.stringtable
VM.symboltable
VM.system_properties
VM.systemdictionary
VM.uptime
VM.version
helpFor more information about a specific command use 'help <command>'.
上面罗列的是进程号为25935的JVM进程支持的jcmd相关命令操作,jcmd pid [options]中的options就是上面列出的所有命令参数选项。下面介绍常用的几个参数选项。
2.查看JVM启动时间VM.uptime
jcmd 25935 VM.uptime

3.打印线程栈信息
jcmd 25935 Thread.print

(九)jstatd:远程主机信息收集
之前的指令只涉及监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。
为了启用远程监控,则需要配合使用jstatd工具。
命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。
jstatd服务器将本机的Java应用程序信息传递到远程计算机。

直接打开jstatd服务器可能会抛出访问拒绝异常,这是因为jstatd程序没有足够的权限,如图所示。

