JVM监控工具使用
-------------------------------------------------------------
jmap 是一个 Java 内存映像工具,用于生成 Java 应用程序的堆转储文件(heap dump)并查看对象内存占用情况,还可以分析 JVM 的垃圾收集器行为。它在排查内存泄漏、查看对象分布以及分析内存占用问题时非常有用。
基本用法
jmap [options] <pid>
1
pid:Java 进程的进程号。可以通过 jps、ps 或者其他系统工具查看。
常用选项
-heap
显示 Java 堆内存的详细信息,包括堆大小、使用情况和垃圾收集器的相关信息。可以用于查看堆内存分代(年轻代、老年代等)使用的状态。
jmap -heap <pid>
1
输出示例:
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 5439488 (5.1875MB)
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
...
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 603979776 (576.0MB)
used = 49638784 (47.33973693847656MB)
free = 554340992 (528.6602630615234MB)
...
Heap Configuration:显示 JVM 堆的配置,包括最小和最大堆大小、年轻代和老年代的大小、Metaspace(元空间)的配置等。
Heap Usage:显示堆内存的使用情况,特别是新生代、老年代的内存占用。
-histo
输出堆中各类 Java 对象的统计信息(对象的类型、数量和大小)。可以用于分析哪个类的对象数量最多,占用内存最大。
jmap -histo <pid>
1
输出示例:
num #instances #bytes class name
----------------------------------------------
1: 10000 2400000 [C
2: 5000 1200000 java.lang.String
3: 4000 960000 java.util.ArrayList
...
#instances:每个类的对象实例数量。
#bytes:这些对象占用的内存大小。
class name:对象所属的类。
通过 -histo 选项,可以快速查看哪些对象占用了大量内存,在排查内存泄漏时非常有用。
-dump
生成 JVM 堆的转储文件(heap dump),通常用于分析内存泄漏。生成的堆转储文件可以使用分析工具如 Eclipse MAT(Memory Analyzer)进行深入分析。
jmap -dump:format=b,file=<file_path> <pid>
1
format=b:指定堆转储文件的格式为二进制(默认格式)。
file=<file_path>:指定转储文件的保存路径。
示例:
jmap -dump:format=b,file=/tmp/heapdump.hprof 12345
1
这个命令会将 Java 进程 12345 的堆内存导出到 /tmp/heapdump.hprof 文件中。
-finalizerinfo
显示正在等待 finalization 队列中的对象。这些对象已经被垃圾收集器标记为可回收,但在它们的 finalize() 方法执行之前不会被清除。
jmap -finalizerinfo <pid>
1
这个命令有助于分析垃圾回收延迟的问题,特别是当大量对象未及时清除时,可以检查是否有大量对象在等待 finalize 处理。
-F
强制执行命令,即使目标 JVM 没有响应或出现问题时也强制获取相关信息。该选项通常与 -dump 或 -histo 结合使用。
jmap -dump:format=b,file=heapdump.hprof -F <pid>
1
-clstats
显示类加载器的统计信息,包括每个类加载器加载的类数量、字节数等。这对于调试类加载器泄漏或内存占用问题非常有用。
jmap -clstats <pid>
1
使用场景
1. 内存泄漏排查
内存泄漏是 Java 程序中常见的问题之一,通过 jmap 可以获取堆转储(heap dump)文件,并结合其他工具(如 Eclipse MAT)进行详细分析。常用步骤如下:
使用 jmap -dump:format=b,file=heapdump.hprof <pid> 生成堆转储文件。
使用内存分析工具(如 MAT)加载 heapdump.hprof 文件,分析对象的占用情况、GC Roots 等,找出内存泄漏源头。
2. 对象分布和内存使用分析
通过 jmap -histo <pid>,可以查看当前 Java 堆中各类对象的数量和内存占用情况。如果某类对象占用了大量内存且不断增长,可能说明该类存在内存泄漏或过多分配。
3. 垃圾回收行为调优
通过 jmap -heap <pid>,可以查看 JVM 堆内存的详细使用情况和垃圾收集器的配置。在内存使用异常时(如频繁 Full GC),可以通过该命令了解各代内存的占用情况,帮助调整垃圾收集器的参数。
4. 检查 finalize() 延迟执行
通过 jmap -finalizerinfo <pid> 可以检查 finalize() 方法是否因为某种原因延迟执行,导致对象无法及时回收。如果有大量对象排队等待 finalize(),可能说明有资源释放方面的问题。
结合 jmap 与其他工具
Eclipse MAT(Memory Analyzer Tool)
用于分析通过 jmap -dump 生成的堆转储文件,MAT 可以帮助你找到哪些对象占用的内存最多,以及是否存在内存泄漏。
jstack
可以结合 jstack 分析线程状态。某些情况下内存泄漏可能与线程阻塞或死锁相关,通过同时查看线程快照和堆转储文件可以帮助全面分析问题。
注意事项
性能影响:在高负载的生产环境中使用 jmap -dump 生成堆转储文件时,可能会导致应用短暂挂起,因此在生产环境中使用时需要小心,通常建议在低负载或预生产环境下执行。
堆转储文件较大:堆转储文件(.hprof)通常非常大,特别是在大内存场景下生成的文件可能会达到数百 MB 或数 GB,因此要确保有足够的磁盘空间。
权限问题:在某些系统中,使用 jmap 可能需要以 root 或具有足够权限的用户身份运行,否则会报权限错误。
总结
jmap 是 JVM 内存分析的重要工具,特别适用于以下场景:
内存泄漏排查:通过堆转储文件和对象分布查看是否有类占用过多内存。
垃圾回收调优:通过查看堆内存使用情况了解 GC 行为和内存分代配置。
检查对象的分配和清除:分析等待 finalize 的对象,检查对象分配是否异常。
-------------------------------------------------------------
-------------------------------------------------------------
1. 描述
程序在开发过程中,有可能会发生CPU飙高、内存溢出等问题或系统在后期调优阶段,不可避免的要监控JVM情况,JDK自带的Jconsole监控工具,结合Tomcat使用非常方便,占用内存小,满足必要的监控项,还有不错的可视化界面,居家必备。
java线程的6种状态
初始态(NEW)
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。
就绪态
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。
所有就绪态的线程存放在就绪队列中。
运行态
获得CPU执行权,正在执行的线程。
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
阻塞态(BLOCKED)
当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
而在Java中,阻塞态专指请求锁失败时进入的状态。
由一个阻塞队列存放所有阻塞态的线程。
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
等待态(WAITING)
当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
也有一个等待队列存放所有等待态的线程。
线程处于等待态表示它需要等待其他线程的指示才能继续运行。
进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
超时等待态(TIMED_WAITING)
当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;
进入该状态后释放CPU执行权 和 占有的资源。
与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
终止态(TERMINATED)
线程执行结束后的状态。
2. 方案
2.1 jconsole介绍
jconsole是JDK自带的工具,在JDK的bin下面有,本人系统的目录是:C:\Program Files\Java\jdk1.8.0_171\bin,进入目录双击:jconsole.exe,或者在cmd下执行都行,双击就会弹出下面的界面。
2.2 jconsole本地启动
本地一般是在IDE开发工具(idea、ecplise),直接选择”本地进程“,双击对应进程就行了,其中名称为启动JVM名称,pid是进程号,例如我选择的是:com.hbusy.Application,双击进入监控页面。
jconsole控制台包含6个页面,分别为:概览、内存、线程、类、VM概要、MBean
2.2.1 概览
2.2.2 内存页面,可以查看jvm不同区占用的内存。
2.2.3 线程
2.2.4 类
2.2.5 VM 概要
2.2.6 Mbean
2.3 jconsole远程访问
2.3.1 参数配置
本人远程服务器是linux+tomcat,需要在tomcat的apache-tomcat-8.0.53/bin的catalina.sh 增加参数配置
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=19107 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=192.168.0.11"
重点就两个参数:
(1) hostname设置为服务器ip地址;
(2) port访问端口号
2.3.2 jconsole远程访问
选择远程进程,然后输入访问地址:192.168.0.11:19107,进入后jconsol控制台后与本地访问控制台界面一样。
JDK自带的工具
我们可以看到各个工具的大小基本上都稳定在27kb左右,这个不是JDK开发团队刻意为之的,而是因为这些工具大多数是jdk\lib\tools.jar类库的一层薄包装而已,他们的主要功能代码是在tools类库中实现的。命令行工具的好处是:当应用程序部署到生产环境后,无论是直接接触物理服务器还是远程telnet到服务器上都会受到限制。而借助tools.jar类库里面的接口,我们可以直接在应用程序中实现功能强大的监控分析功能。
常用命令:
这里主要介绍如下几个工具:
1、jps:查看本机java进程信息
2、jstack:打印线程的栈信息,制作 线程dump文件
3、jmap:打印内存映射信息,制作 堆dump文件
4、jstat:性能监控工具
5、jhat:内存分析工具,用于解析堆dump文件并以适合人阅读的方式展示出来
6、jconsole:简易的JVM可视化工具
7、jvisualvm:功能更强大的JVM可视化工具
8、javap:查看字节码
JAVA Dump:
JAVA Dump就是虚拟机运行时的快照,将虚拟机运行时的状态和信息保存到文件中,包括:
线程dump:包含所有线程的运行状态,纯文本格式
堆dump:包含所有堆对象的状态,二进制格式
1、jps
显示当前所有java进程pid的命令,我们可以通过这个命令来查看到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例),不过jps有个缺点是只能显示当前用户的进程id,要显示其他用户的还只能用linux的ps命令。
执行jps命令,会列出所有正在运行的java进程,其中jps命令也是一个java程序。前面的数字就是进程的id,这个id的作用非常大,后面会有相关介绍。
jps -help:
jps -l 输出应用程序main.class的完整package名或者应用程序jar文件完整路径名
jps -v 输出传递给JVM的参数
jps失效
我们在定位问题过程会遇到这样一种情况,用jps查看不到进程id,用ps -ef | grep java却能看到启动的java进程。
要解释这种现象,先来了解下jps的实现机制:
java程序启动后,会在目录/tmp/hsperfdata_{userName}/下生成几个文件,文件名就是java进程的pid,因此jps列出进程id就是把这个目录下的文件名列一下而已,至于系统参数,则是读取文件中的内容。
我们来思考下:如果由于磁盘满了,无法创建这些文件,或者用户对这些文件没有读的权限。又或者因为某种原因这些文件或者目录被清除,出现以上这些情况,就会导致jps命令失效。
如果jps命令失效,而我们又要获取pid,还可以使用以下两种方法:
1、top | grep java
2、ps -ef |grep java
2、jstack
主要用于生成指定进程当前时刻的线程快照,线程快照是当前java虚拟机每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。
3、jmap
主要用于打印指定java进程的共享对象内存映射或堆内存细节。
堆Dump是反映堆使用情况的内存镜像,其中主要包括系统信息、虚拟机属性、完整的线程Dump、所有类和对象的状态等。一般在内存不足,GC异常等情况下,我们会去怀疑内存泄漏,这个时候就会去打印堆Dump。
jmap的用法摘要:
1、jmap pid
打印的信息分别为:共享对象的起始地址、映射大小、共享对象路径的全程。
2、jmap -heap pid:查看堆使用情况
3、jmap -histo pid:查看堆中对象数量和大小
打印的信息分别是:序列号、对象的数量、这些对象的内存占用大小、这些对象所属的类的全限定名
如果是内部类,类名的开头会加上*,如果加上live子参数的话,如jmap -histo:live pid,这个命名会触发一次FUll GC,只统计存活对象
4、jmap -dump:format=b,file=heapdump pid:将内存使用的详细情况输出到文件
然后使用jhat命令查看该文件:jhat -port 4000 文件名 ,在浏览器中访问http:localhost:4000/
总结:
该命令适用的场景是程序内存不足或者GC频繁,这时候很可能是内存泄漏。通过用以上命令查看堆使用情况、大量对象被持续引用等情况。
4、jstat
主要是对java应用程序的资源和性能进行实时的命令行监控,包括了对heap size和垃圾回收状况的监控。
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
option:我们经常使用的选项有gc、gcutil
vmid:java进程id
interval:间隔时间,单位为毫秒
count:打印次数
1、jstat -gc PID 5000 20
S0C:年轻代第一个survivor的容量(字节)
S1C:年轻代第二个survivor的容量(字节)
S0U:年轻代第一个survivor已使用的容量(字节)
S1U:年轻代第二个survivor已使用的容量(字节)
EC:年轻代中Eden的空间(字节)
EU:年代代中Eden已使用的空间(字节)
OC:老年代的容量(字节)
OU:老年代中已使用的空间(字节)
PC:永久代的容量
PU:永久代已使用的容量
YGC:从应用程序启动到采样时年轻代中GC的次数
YGCT:从应用程序启动到采样时年轻代中GC所使用的时间(单位:S)
FGC:从应用程序启动到采样时老年代中GC(FULL GC)的次数
FGCT:从应用程序启动到采样时老年代中GC所使用的时间(单位:S)
2、jstat -gcutil PID 5000 20
s0:年轻代中第一个survivor已使用的占当前容量百分比
s1:年轻代中第二个survivor已使用的占当前容量百分比
E:年轻代中Eden已使用的占当前容量百分比
O:老年代中已使用的占当前容量百分比
P:永久代中已使用的占当前容量百分比
5、jhat
主要用来解析java堆dump并启动一个web服务器,然后就可以在浏览器中查看堆的dump文件了。
生成dump文件的方法前面已经介绍了,这边主要介绍如何解析java堆转储文件,并启动一个web server
jhat heapdump
这个命令将heapdump文件转换成html格式,并且启动一个http服务,默认端口为7000。
如果端口冲突,可以使用以下命令指定端口:jhat -port 4000 heapdump
下面我们来访问下:ip:port
6、jinfo
jinfo可以用来查看正在运行的java运用程序的扩展参数,甚至支持在运行时动态地更改部分参数。
基本使用语法如下: jinfo -< option > < pid > ,其中option可以为以下信息:
-flag< name >: 打印指定java虚拟机的参数值
-flag [+|-]< name >:设置或取消指定java虚拟机参数的布尔值
-flag < name >=< value >:设置指定java虚拟机的参数的值
使用示例
下面的命令显示了新生代对象晋升到老年代对象的最大年龄。在运行程序运行时并没有指定这个参数,但是通过jinfo,可以查看这个参数的当前的值。
下面的命令显示是否打印gc详细信息:
下面的命令在运用程序运行时动态打开打印详细gc信息开关:
注意事项:jinfo虽然可以在java程序运行时动态地修改虚拟机参数,但并不是所有的参数都支持动态修改。
7、jcmd
在JDK 1.7之后,新增了一个命令行工具jcmd。它是一个多功能工具,可以用来导出堆,查看java进程,导出线程信息,执行GC等。jcmd拥有jmap的大部分功能,Oracle官方建议使用jcmd代替jmap。
使用 jcmd -l 命令列出当前运行的所有虚拟机,示例:
针对每一个虚拟机,可以使用help命令列出该虚拟机支持的所有命令,示例:
子命令含义:
- VM.native_memory
- VM.commercial_features
- GC.rotate_log
- ManagementAgent.stop
- ManagementAgent.start_local
- ManagementAgent.start
- Thread.print, 打印线程栈信息
- GC.class_histogram, 查看系统中类统计信息
- GC.heap_dump, 导出堆信息,与jmap -dump功能一样
- GC.run_finalization, 触发finalize()
- GC.run, 触发gc()
- VM.uptime, VM启动时间
- VM.flags, 获取JVM启动参数
- VM.system_properties, 获取系统Properties
- VM.command_line, 启动时命令行指定的参数
- VM.version
- help

8、可视化监控工具(JConsole、JVisualVM)
集上面之大成,并提供了可视化的界面;还可以监控远程Java服务;支持监控JMX。
JVisualVM比JConsole更强大:支持对CPU、内存运行进行采样、配置。推荐用JVisualVM。
JConsole监控页面示例:
JVisualVM监控页面示例: