Java性能监控与故障处理分析工具(Heap memory和Native memory)
文章目录
前言
在实际的工作中会不断的遇到各种线上问题,包括但不限于内存溢出、服务响应时间上升、线程block等各种情况,这时候就需要性能监控以及故障处理工具来分析和定位问题,google后发现并没有一篇分类清晰、实用的文章能够概括以上对于工具的需求,因此,决定整理一下相关的内容,以供大家学习参考。
本文意在介绍了有哪些常用的分析工具,以方便大家能够快速的分析定位问题。@空歌白石
文章的整个编写思路可以通过如下思维导图所示:
这里有几点需要说明:
- JDK原生工具特别是
jmc
以及arthas
是在线实时分析工具,mat
是离线分析工具,jProfiler
也可以支持离线和实时分析。 - 以上工具都是很大篇幅是针对堆内内存进行分析,对于堆外内存的问题分析能力十分有限甚至可以说没有。
- 如果如果确定是堆外内存问题,一般堆外内存溢出排查可结合
gperftools
与btrace
排查。
Java process memory
在介绍工具前,先来了解下Java进程内存的分类:
- Heap memory(堆内存)
- 供Java应用程序使用。
- Heap memory 存在于 JVM 进程中,并用于保存 Java 对象,有 JVM 垃圾收集器维护的内存。
- Heap Memory 及其内部各组成的大小可以通过JVM的一系列命令行参数来控制,也就是一般意义上提到的JVM参数(不同的GC参数也会不同)。
- Native memory/Off-heap(堆外内存)
- 供JVM自身进程使用。
- Native memory 是在进程地址空间内分配的内存,由操作系统负责。由于不在堆内,因此不会被 Java 垃圾收集器释放和管理。
- Native Memory 没有相应的参数来控制大小,其大小依赖于操作系统进程的最大值(对于32位系统就是3~4G,各种系统的实现并不一样),以及生成的Java字节码大小、创建的线程数量、维持java对象的状态信息大小(用于GC)以及一些第三方的包,比如JDBC驱动使用的native内存。
除了以上来两种内存外,还经常会提到Direct memory,Direct memory类似于Native memory,会使用共享硬件内的底层缓冲区。例如,网络IO的缓冲区,设置Direct memory
是减少相同字节在内存中被复制的次数。
Native Memory
为什么会有Native Memory?以下是Native Memory的一些职责。
- 管理java heap的状态数据(用于GC);
- JNI调用,也就是Native Stack;
- JIT(即使编译器)编译时使用Native Memory,并且JIT的输入(Java字节码)和输出(可执行代码)也都是保存在Native Memory;
- NIO direct buffer。对于IBM JVM和Hotspot,都可以通过-XX:MaxDirectMemorySize来设置nio直接缓冲区的最大值。默认是64M。超过这个时,会按照32M自动增大。
- 对于IBM的JVM某些版本实现,类加载器和类信息都是保存在Native Memory中的。
Direct Memory
Direct Memory
在Java中都是由DirectBuffer
来负责维护的,DirectBuffer访问更快,避免了从HeapBuffer还需要从java堆拷贝到本地堆,操作系统直接访问的是DirectBuffer。DirectBuffer对象的数据实际是保存在native heap中,但是引用保存在HeapBuffer中。这也就是我们经常会遇到的一个问题,堆内内存很稳定,但是堆外的内存一直释放不掉,需要看下保存在HeapBuffer
的引用是否已经被去掉了。
另外,DirectBuffer的引用是直接分配在堆得Old区的,因此其回收时机是在FullGC时。因此,需要避免频繁的分配DirectBuffer,这样很容易导致Native Memory溢出。
Direct Memory
并非 JVMS 定义的标准 Java 运行时内存。- JDK1.4 加入了新的 NIO 机制,目的是防止 Java 堆 和 Native 堆之间往复的数据复制带来的性能损耗,此后 NIO 可以使用 Native 的方式直接在 Native 堆分配内存。
Direct Memory
区域是全局共享的内存区域。Direct Memory
区域可以进行自动内存管理(GC),但机制并不完善。- 本机的 Native 堆(直接内存) 不受 JVM 堆内存大小限制。
- 可能出现 OutOfMemoryError 异常。
OOM
Java进程内存由Heap memory
和Native memory
组成,也就是堆内内存和堆外内存。两块内存空间都有可能发生OOM。因此在遇到OOM的异常时,首先要区分是哪块内存空间发生了OOM,然后在针对性的定位。但不论怎样Native Memory
发生OOM本质上就是因为耗尽了进程地址空间。
如果发现进程内存过高,可能需要注意下程序是否存在堆外内存泄漏问题,java进程出现的堆外内存泄漏常见有如下几个方面:
- JNI
- NIO directbuffer泄漏
- 网络编程中程序中,
NIO directbuffer
有监控工具可以查看,directbuffer
使用量、并且可以设置最大直接内存buffer。
- 网络编程中程序中,
- gzip
下文中讲到的JDK原生工具
、arthas
、mat
、JProfiler
都是针对Heap memory
的,而针对Native memory
请查看Native memory analysis
小节。
堆外内存的优点
使用堆外内存有很多优点,但是对于开发人员的要求更高,因为没有JVM的GC来负责,对于内存的控制需要更加精确和小心,否则很容易就会造成堆内和堆外内存的OOM,并且排查起来也会很困难。
- 减少了垃圾回收
- 因为垃圾回收会暂停其他的工作。
- 加快了复制的速度
- 堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。
JDK原生工具
JDK本身提供了丰富的监控工具,拥有强大的功能。在监控方面包含两大类:一类是监控应用的,一类是监控JVM的,在故障排查中也有诸多的工具可以用。
官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/toc.html
本文使用的Jdk版本:
|
|
jdk/bin目录下包含了全部的tools集合。
Troubleshooting(排障工具)
jinfo
jinfo: Java配置信息工具,Configuration Info for Java
,显示虚拟机配置信息,可以实时地查看和调整虚拟机各项参数。
- 使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了(如果只限于 JDK 1.6或以上版本的话,使用java -XX : +PrintFlagsFinal查看参数默认值也是一个很好的选择 );
- jinfo还可以使用-sysprops选项把虚拟机进程的System.getProperties() 的内容打印出来。
jinfo
在JDK 1.5时期已经随着Linux版的JDK发布,当时只提供了信息查询的功能,JDK1.6之后,jinfo在Windows和Linux平台都有提供,并且加入了运行期修改参数的能力,可以使用-flag[+|-jname
或者-flag name=value
修改一部分运行期可写的虚拟机参数值。JDK 1.6中,jinfo对于Windows平台功能仍然有较大限制,只提供了最基本的-flag选项。
jinfo命令格式
|
|
|
|
使用ps -ef | grep 'java'
查看当前运行应用的PID。执行命令jinfo [option] pid
后,可以看到当前JVM的运行参数,主要包含四部分:
- Java System Properties
- 使用
jinfo -sysprops pid
命令单独查看
- 使用
- VM Flags
- 使用
jinfo -flags pid
命令单独查看
- 使用
- VM Arguments
- Launcher Type
在最后的参数会有Launcher Type: SUN_STANDARD
说明当前标准的Launcher。
查看java进程的更为丰富的指令ps -ef | grep java | grep -v grep | awk '{printf "%d\t%s",$2,$8}'
。
jmap
jmap
: Java内存映像工具,Memory Map for Java
,生成虚拟机的内存转储快照(一般称为heapdump或dump文件)
如果不使用jmap
命令,要想获取Java堆转储快照,还有一些比较暴力的手段:
- 譬如
-XX:+HeapDumpOnOutOfMemoryError
参数,可以让虚拟机在OOM异常出现之后自动生成dump文件- 此方法在频繁发生OOM需要准确捕获当时现场的需求场景下十分有效。但是并不推荐生产的全部机器都配置,仅仅配置1-2台能够抓取dump即可
- 还需要注意一点是,由于某些情况下dump文件会很大,因此需要预留足够的磁盘空间。
- 通过
-XX:+HeapDumpOnCtrlBreak
参数则可以使用[Ctrl]+[Break]键让虚拟机生成dump文件 - 又或者在Linux系统下通过
Kill -3
命令发送进程退出信号吓唬下虚拟机,也能拿到dump文件
jmap
的作用并不仅仅是为了获取dump文件,它还可以查询finalize
执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。
和jinfo
命令一样,jmap
有不少功能在Windows平台下都是受限的,除了生成dump文件的-dump
选项和用于查看每个类的实例、空间占用统计的-histo
选项在所有操作系统都提供之外,其余选项都只能在Linux/Solaris下使用。但是经过在本地测试,jdk1.8
在Windows平台下支持的参数不仅仅是上面两个。
jmap
或jmap -h
命令可以查看jmap
的全部参数。
|
|
jmap -dump
jmap –dump:[live, ]format=b, file=<filename>
,-dump
参数应该是jmap
命令最常用的部分,可以生成当前运行时的堆内存文件。
实际运行jmap -dump:live,format=b,file=/opt/logs/dump.hprof 37
。
|
|
可以看到文件已经生成。
|
|
文件传输
接下来就是可以使用sz dump.hprof
或者通过sftp可以将dump文件转移到其他可以分析dump的机器上进一步分析。
这里可能有个问题,如果dump文件较小使用sz
命令还可以轻松处理,但是当dump有十几个GB甚至上百GB的时候会很难轻松传输。这个问题可以通过临时搭建一个httpServer来解决。
|
|
**注意:**上述指令需要到dump文件所在目录执行,8989可以替换为任意可用端口。
- 如果使用
sz
可以轻松将文件传输到本地 - 如果使用HttpServer的话
- 对于Widnows系统,通过域名加端口,浏览器直接下载即可
- 对于Linux系统,可以使用
sudo wget http://ip:port/dump.hprof
方式下载
jmap -clstats
jmap -clstats {pid}
查看当前class的统计情况。
|
|
一般情况下我们使用以下命令打印当前Class信息,并输出到指定的文件中。
|
|
- loginUser:用当前登录用户替换
- pid:上文中获取的PID
jmap -finalizerinfo
jmap -finalizerinfo {pid}
,查询finalize
执行队列绝大部分情况,应该是如下信息。
|
|
jmap -histo
jmap -histo {pid}
,可以查看具体class的实例数量以及所占用的堆大小。
|
|
可以添加grep
过滤结果,如jmap -histo 37 | grep 'sun'
|
|
jhat
jhat
: 堆转储快照分析工具,JVM Heap Analysis Tool
的缩写,用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果。
使用命令jhat dump.hprof
。
实际工作中,一般不会直接使用 jhat 命令来分析 dump 文件,主要原因:
- 一般不会在生产环境直接分析 dump 文件,主要包括几方面原因:
- 分析工作一般比较耗时,而且消耗硬件资源,生产环境一般内存仅仅够程序正常运行使用,无法满足dump的分析需求
- 如果在其他机器上进行时则没必要受到命令行工具的限制
jhat
分析功能相对简陋,大部分情况下会使用后续介绍的Java VisualVM
、Eclipse Memory Analyzer (mat)
等更功能更加强大可视化工具分析。
因此,这里只是了解有这个命令即可,无需过多关注。
jstack
jstack
: Java堆栈跟踪工具,Stack Trace for Java
,生成虚拟机当前时刻的线程快照(一般称为 threaddump
或 javacore
文件)。
线程快照
就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成堆栈快照的主要目的是定位线程出现长时间停顿的原因,如线程死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。
jstack
命令说明:
|
|
一般使用时可按如下方式使用:加上more
可以不必一下子展示出全部堆栈信息,引起刷屏,通过回车来分屏查看。
|
|
实际案例
|
|
jcmd
jcmd
是在JDK1.7以后,新增了一个命令行工具。jcmd
是一个多功能的工具,可以用它来导出堆、查看Java进程、导出线程信息、执行GC、还可以进行采样分析(jmc 工具的飞行记录器)。
|
|
查看当前Java进程
|
|
jcmd
,jcmd -l
,jps
三个命令都可以达到查看当前Java进程的效果。
性能统计
jcmd ${pid} PerfCounter.print
,查看指定进程的性能统计信息。
|
|
当前运行的 java 进程可以执行的操作
jcmd ${pid} help
使用这个命令,可以查看jcmd
可以执行哪些操作,可以理解过对其他部分指令的一个封装。
|
|
举个例子:使用jcmd 37 VM.uptime
查看VM的运行时长
|
|
这里列举几个等价的命令:
jcmd PID GC.class_histogram
,查看系统中类统计信息,等价于jmap -histo pid
。jcmd PID Thread.print
,查看线程堆栈信息,等价于jstack -l pid
。jcmd PID GC.heap_dump FILE_NAME
,查看 JVM 的Heap Dump,等价于jmap -dump:format=b,file=heapdump.phrof pid
。
jsadebugd
命令格式:jsadebugd PID [ServerId]
。
|
|
使用jinfo,jmap,jstack命令不仅可监控本地主机上的JVM进程,也可监控远端主机上的JVM进程,前者称为“本地模式”,后者称为“联网模式”。“联网模式”下的整个监控体系实质上是一个RMI应用程序。在“联网模式”下,一般情况下,远端主机上“运行rmiregistry服务,运行RMI Server”,本地主机上“运行RMI Client”,整个体系中“无需运行Web Server服务(因为所有相关类都可在本地获取)”。
在远端主机上,通过运行jsadebugd命令开启RMI Server(需要注意的是,运行jsadebugd命令同时会自动运行rmiregistry服务,而无需手动运行),使用“PID”选项值指定远端主机上待被监控的JVM进程ID,使用“ServerId”选项值作为RMI Server内向rmiregistry服务注册所生成Remote Object实例的名称(如果未指定,则使用默认名称);在本地主机上,运行jinfo,jmap,jstack命令作为RMI Client。
jsadebugd
在JDK11中已经没有这个命令,这里不再过多介绍。
Monitor the JVM
jps
jps
:虚拟机进程状况工具,JVM Process Status Tool
的缩写,显示指定系统内所有的HotSpot虚拟机进程。jps
可以有什么用处呢?
- 可以列出正在运行的虚拟机进程;
- 显示虚拟机执行主类(main函数坐在的类)名称;
- 显示进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID);
-
jps是使用频率最高的JDK命令行工具,因为其他的JDK工具大多数需要输入它查询到的LVMID来确定要监控的是哪一个虚拟机进程。
-
对于本地虚拟机进程来说,LVMID与操作系统的进程ID ( Process Identifier,PID ) 是一致的。
-
使用Windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID ,但如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就只能依赖jps命令显示主类的功能才能区分了。
-
jps命令格式: jps [options] [hostid]
|
|
- jps可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,hostid为RMI注册表中注册的主机名。
- 需要远程主机提供RMI支持,Sun提供的jstatd工具可以很方便地建立远程RMI服务器。
各个参数执行后详情:
|
|
jstat
jstat: 虚拟机统计信息监视工具,JVM Statistics Monitoring Tool
的缩写,用于收集HotSpot虚拟机各方面的运行数据。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
jstat
命令格式:
|
|
|
|
|
|
- 对于命令格式中的
VMID
与LVMID
需要特别说明一下:- 如果是本地虚拟机进程,
VMID
与LVMID
是一致的; - 如果是远程虚拟机进程,那
VMID
的格式应当是: [protocol:][//]lvmid[@hostname[:port]/servername]
- 如果是本地虚拟机进程,
- 参数
interval
和count
代表查询间隔
和次数
,如果省略这两个参数,说明只查询一次
。- 假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:
jstat -gc 2764 250 20
- 假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:
- 选项
option
代表着用户希望查询的虚拟机信息,主要分为3类:类装载、垃圾收集、运行期编译状况。- 类装载
-class
:监视类装载、卸载数量、总空间以及类装载所耗费的时间。
- 垃圾收集
-gc
:监视Java堆状况,包括Eden区、两个survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息-gccapacity
:监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间-gcutil
:监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比-gccause
:与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因-gcnew
:监视新生代GC状况-gcnewcapacity
:监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间-gcold
:监视老年代GC状况-gcoldcapacity
:监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间-gcpermcapacity
:输出永久代使用到的最大、最小空间
- 运行期编译状况
-compiler
:输出JIT编译器编译过的方法、耗时等信息-printcompilation
:输出已经被JIT编译的方法
- 类装载
GC优化中,利用jstat -gc ${pid}
较多,实际执行如下:
|
|
每隔一秒打印GC信息,输出10次。jstat -gc 37 1000 10
|
|
具体的参数说明:
参数 | 说明 |
---|---|
S0C | 年轻代中第一个survivor区的容量(字节) |
S1C | 年轻代中第二个survivor区的容量(字节) |
S0U | 年轻代中第一个survivor区目前已使用空间(字节) |
S1U | 年轻代中第二个survivor区目前已使用空间(字节) |
EC | 年轻代中Eden区的容量 (字节) |
EU | 年轻代中Eden区目前已使用空间(字节) |
OC | 老年代的容量(字节) |
OU | 老年代目前已使用空间(字节) |
PC | Perm(永久代)的容量(字节) |
PU | Perm(永久代)目前已使用空间(字节) |
YGC | 从应用程序启动到采样时年轻代中gc次数 |
YGCT | 从应用程序启动到采样时年轻代中gc所用时间(s) |
FGC | 从应用程序启动到采样时老年代(全gc)gc次数 |
FGCT | 从应用程序启动到采样时老年代(全gc)gc所用时间(s) |
GCT | 从应用程序启动到采样时gc用的总时间(s) |
jstat -class
命令执行情况。
|
|
jstat -compiler
命令执行情况。
|
|
jmc
jmc
是Java Mission Control
的缩写,是一个集成了分析、监视和诊断等功能的工具套件,可以用于对HotSpot JVM
进行生产时间分析和诊断的工具。jmc
的两个主要功能是管理控制台
和Java飞行记录仪
,但还有一些功能是作为插件提供的,可以从该工具下载。jmc
也可以作为Eclipse IDE的一组插件使用。
官方文档:https://docs.oracle.com/en/java/java-components/jdk-mission-control/8/user-guide/
JMC的两大功能如下图:
其中MBean服务器
是免费的,飞行记录器(Java Flight Recorder)
是需要收费的。
服务端如何开启jmc
首先,JDK从JDK8-u265版本开始支持jmc
,如果需要想要使用jmc
需要先明确jdk的版本,如果过低需要升级才可以。
在确定JDK版本支持jmc后,需要在/tomcat/bin/setenv.sh
文件的JAVA_OPTS
属性中添加-XX:StartFlightRecording \
这样一句话。可以直接手动修改,也可以使用sed
命令添加内容,如下:
|
|
添加后效果如下:
|
|
修改配置后,重启tomcat容器。
|
|
属性介绍:
-Dcom.sun.management.jmxremote
- 此选项用于切换 JMXRMI 的使用。默认情况下启用该选项,因此无需设置该选项就可以使用 JVM 的监视功能。要为 JVM 禁用 JMXRMI,请指定 -Dcom.sun.management.jmxremote=false。
-Dcom.sun.management.jmxremote.authenticate
- 该选项用于切换对 JMXRMI 连接使用口令验证。默认情况下此选项是启用的。要为 JVM 禁用口令验证,请指定 -Dcom.sun.management.jmxremote.authenticate=false。
- 注意:禁用验证是不安全的。任何知道 (或猜中) JMX 端口号和主机名的远程用户都能够监视和控制 Java 应用程序和 VM。虽然开发时这是可接受的,但对于生产系统不建议这样设置。
-Dcom.sun.management.jmxremote.port
` - 此选项用于设置 JMXRMI 连接的端口号。必须设置未使用的端口号才能启用 JVM 的监视和管理功能。-Dcom.sun.management.jmxremote.ssl
- 此选项用于切换安全套接字层 Sockets Layer (SSL) 验证的使用。默认情况下启用该选项。要为 JVM 禁用 SSL 验证,请指定 -Dcom.sun.management.jmxremote.ssl=false。
jmc在哪里
从Java11开始,Java分为了OracleJDK和AdoptOpenJDK两个版本,目前由于版权问题,都要求使用开源的AdoptOpenJDK
,这里就有一个问题,在AdoptOpenJDK
的bin
目录下是找不到jmc
应用的。有几个方法可以解决
- 如果安装过JDK8-u265以上版本,可以使用jdk8 bin目录下的
jmc
,这个不受服务端jdk版本影响,也就是说如果应用是jdk11,我用jdk 8的jmc仍然可以进行问题分析。 - 如果只安装了jdk11甚至更高版本的jdk。可以单独下载jmc
- openjdk的jmc官网地址:https://openjdk.org/projects/jmc/
- github地址:https://github.com/openjdk/jmc
- Oracle的jmc下载:https://www.oracle.com/java/technologies/jdk-mission-control.html
- openjdk的jmc官网地址:https://openjdk.org/projects/jmc/
MBean服务器
详细的使用可以参考:JVM——jmc使用说明,以下部分内容也援引此文。
创建新的连接。
overview
默认布局提供 CPU 和内存使用情况的概览。
概览:可以添加自定义图表,通过概览的加号”添加图表”实现;可以重置,通过”重置为默认控件”实现。 添加图表后,可以通过图表的加号添加相应的子项,并可以在图表上右键详细设置。
mbean-brower
JMC使用托管Bean (MBean) 为监视和管理 Java 应用程序性能提供统一且一致的界面。MBean 是采用符合 JMX 规范的设计模式的托管对象。MBean 可以表示一个设备、一个应用程序或需要托管的任何资源。MBean 的管理界面由一组属性、操作和通知组成。
MBean功能:
- 属性:列出所选 MBean 的属性。
- 操作:列出可从所选 MBean 调用的操作。
- 通知:列出在运行时期间 MBean 所提示的通知。
- 元数据:包含描述 MBean 的信息。
trigger
使用触发器选项卡可以管理满足特定条件时触发事件的规则。这是一种无需持续地监视应用程序即可跟踪运行时问题的有用方法。以灰色显示的规则在监视的 JVM 中不可用。默认情况下,停用所有规则。要激活某个规则,请选中该规则旁边的复选框。
system
系统选项卡提供了运行 JVM 的系统的信息、JVM 的性能属性以及系统属性列表。
- 服务器信息
- 在系统选项卡顶部的服务器信息面板中,包含运行 JVM 的服务器的类别和值列表。这些信息对调试应用程序开发问题和运行时问题以及提出支持请求非常有用。这是一般系统信息,不能更改。
- JVM 统计信息
- JVM 统计信息面板包含 JVM 主要性能属性的当前值。默认情况下,表中显示以下属性:
- 当前已加载类计数
- 运行时间
- 要向表中添加属性,请单击 JVM 统计信息面板右上角的添加属性按钮。要删除属性,请在表中右键单击该属性,然后选择删除。右键单击属性后,可以更改其更新间隔、单位,而对于一些属性,还可以设置值。
- 系统属性
- 系统属性面板包含一个表,其中列出使用 JVM 调用的所有属性的关键字和值。要筛选属性,请选择筛选列 (关键字或值),并在表上方的文本字段中指定筛选器字符串。
- 要配置表外观,请单击系统属性面板右上角的表设置按钮,然后选择要显示或隐藏的列。对于各列,可以设置最小宽度、比例和初始排序顺序。展示的是一般系统信息,不能更改。
memory
使用内存选项卡可以监视应用程序使用内存资源的效率。此选项卡主要提供以下方面的信息:堆使用量、垃圾收集和活动内存池。此选项卡上提供的信息可帮助确定是否已将 JVM 配置为提供最佳应用程序性能。
- GC表
- GC 表面板包含可用垃圾收集器的主要性能属性的当前值。该面板分为标有活动垃圾收集器名称的多个选项卡
- 总收集时间
- 收集计数
- GC 开始时间
- GC 结束时间
- GC 持续时间
- GC ID
- GC 线程计数
- GC 表面板包含可用垃圾收集器的主要性能属性的当前值。该面板分为标有活动垃圾收集器名称的多个选项卡
- 活动内存池
- 活动内存池面板包含一个表,其中列出可供 JVM 使用的内存池的信息。默认情况下,该表包含以下列:
- 池名称:内存池的名称。
- 类型:内存池的类型。如果内存池属于 Java 堆,则类型为 HEAP,否则为 NON_HEAP。
- 已用:当前已用的内存池大小。
- 最大值:内存池的最大大小。
- 占用率:当前使用量占最大内存池大小的百分比。
- 已用峰值:在受监视 JVM 的有效期内内存池已用内存的峰值。
- 最大值峰值:在受监视 JVM 的有效期内最大内存池大小的峰值。
- 活动内存池面板包含一个表,其中列出可供 JVM 使用的内存池的信息。默认情况下,该表包含以下列:
thread
使用线程选项卡可以监视线程活动。此选项卡包含一个绘制应用程序随时间推移的活动线程使用情况的图形、一个由该应用程序使用的所有活动线程的表以及选定线程的堆栈跟踪。
- 活动线程图
- 活动线程图显示 Java 应用程序随时间推移启动的线程数。默认情况下,图形中显示以下属性:
- 高峰活动线程计数
- 活动线程总计数
- 守护程序活动线程计数
- 活动线程图显示 Java 应用程序随时间推移启动的线程数。默认情况下,图形中显示以下属性:
- 活动线程
- 活动线程面板包含一个表,其中列出 Java 应用程序所启动活动线程的信息。默认情况下,该表包含以下列:
- 线程名称:线程的名称。
- 线程状态:线程的状态。线程可以是以下状态之一:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 或 TERMINATED。
- 受阻计数:线程处于 BLOCKED 状态的次数。
- CPU 总体占用率:线程所使用 CPU 资源占总 CPU 资源的百分比。默认情况下不提取此值。要启用它,请在表上方选择 CPU 概要分析。
- 死锁:线程是否已死锁。默认情况下不提取此值。要启用它,请在表上方选择死锁检测。
- 已分配的字节:已分配给线程的字节数。默认情况下不提取此值。要启用它,请在表上方选择分配。
- 活动线程面板包含一个表,其中列出 Java 应用程序所启动活动线程的信息。默认情况下,该表包含以下列:
- 选定线程的堆栈跟踪
- 在活动线程表中选择线程后,其整个堆栈跟踪将显示在下面的选定线程的堆栈跟踪面板中。堆栈跟踪包含所有方法 (一直到当前执行的方法) 的调用路径。它非常有用,例如,当需要确定导致死锁或代码执行过程中意外中断的方法时。
command
使用诊断命令可监视 Java 应用程序的效率和性能。JMC 使用大量不同的诊断工具,包括一组可以使用诊断命令选项卡针对应用程序运行的命令。
jstatd
jstatd
用于启动jvm监控服务。它是一个基于rmi的应用,向远程机器提供本机jvm应用程序的信息。默认端口1099。只能够监控具有适当的本地访问权限的JVM。因此,jstatd进程必须以与目标JVM相同的用户凭证来运行。
jstatd
使用的并不多,只作为了解即可。
Monitor Java Applications
JConsole
是在JDK 1.5时期就已经提供的虚拟机监控工具,而Java VisualVM
在JDK 1.6 Update7中才首次发布,现在已经成为Sun ( Oracle ) 主力推动的多合一故障处理工具,并且已经从JDK中分离出来成为可以独立发展的开源项目。
jconsole
jconsole
(Java Monitoring and Management Console): JVM 性能监控,是—种基于JMX的可视化监视管理工具。它管理部分的功能是针对JMX MBean进行管理,由于MBean可以使用代码、中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问,所以本节将会着重介绍JConsole监视部分的功能。
jconsole
逐步被jvisualvm
替代,因此这里只需要了解有jconsole
即可,就不做过多的展开说明。
VisualVM(All-in-One Java Troubleshooting Tool)
Java VisualVM
也被称为jvisualvm
或VisualVM
,是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序,并且可以预见在未来一段时间内都是官方主力发展的虚拟机故障处理工具。它拥有自己独立的官网:https://visualvm.github.io/index.html。
官方在VisualVM的软件说明中写上了All-in-One的描述字样,预示着它除了运行监视、故障处理外,还提供了很多其他方面的功能,如性能分析(Profiling),VisualVM的性能分析功能甚至比起JProfiler、YourKit等专业且收费的Profiling工具都不会逊色多少。
而且VisualVM的还有一个很大的优点:不需要被监视的程序基于特殊 Agent运行,因此它对应用程序的实际性能的影响很小,使得它可以直接应用在生产环境中。这个优点是JProfiler、YourKit等工具无法与之媲美的。
VisualVM基于NetBeans平台开发,因此它一开始就具备了插件扩展功能的特性,通过插件扩展支持,VisualVM可以做到:
- 显示虚拟机进程以及进程的配置、环境信息(jps、 jinfo)。
- 监视应用程序的CPU、GC、堆、方法区以及线程的信息(jstat、jstack)。
- dump以及分析堆转储快照(jmap、jhat)。
- 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法。
- 离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立个快照, 可以将快照发送开发者处进行Bug反馈。
- 其他plugins的无限的可能性
- 现有插件官网地址:https://visualvm.github.io/plugins.html,可以选择自己需要的合适插件。
BTrace
BTrace是一个很有趣的VisualVM插件,可以负责动态日志跟踪,本身也是可以独立运行的程序。
它的作用是在不停止目标程序运行的前提下,通过HotSpot虚拟机的HotSwap技术动态加入原本并不存在的调试代码。
这项功能对实际生产中的程序很有意义:经常遇到程序出现问题,但排查错误的一些必要信息,譬如方法参数、返回值等,在开发时并没有打印到日志之中,以至于不得不停掉服务,通过调试增量来加入日志代码以解决问题。当遇到生产环境服务无法随便停止时,缺一两句日志导致排错进行不下去是一件非常郁闷的事情。
这里额外提一点,BTrace是后文中准备介绍的arthas的基础开源框架,arthas也是在BTrace基础上升级而来。
arthas
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
arthas官方地址:https://arthas.aliyun.com/doc/quick-start.html
github地址:https://github.com/alibaba/arthas
start arthas
在命令行下面执行:
|
|
使用和目标进程一致的用户启动,否则可能 attach 失败。
运行截图:
help
arthas启动后,可以通过help
命令查看有哪些指令可使用,可以作为临时的开发手册。后续只是简单介绍一些常用的命令,未介绍到的可以翻阅官方文档使用,使用起来都很方面简洁。
命令列表:https://arthas.aliyun.com/doc/commands.html
|
|
jvm相关
dashboard
当前系统的实时数据面板。dashboard官方文档
|
|
每一列的说明:
- ID: Java 级别的线程 ID,注意这个 ID 不能跟 jstack 中的 nativeID 一一对应。
- NAME: 线程名
- GROUP: 线程组名
- PRIORITY: 线程优先级, 1~10 之间的数字,越大表示优先级越高
- STATE: 线程的状态
- CPU%: 线程的 cpu 使用率。比如采样间隔 1000ms,某个线程的增量 cpu 时间为 100ms,则 cpu 使用率=100/1000=10%
- DELTA_TIME: 上次采样之后线程运行增量 CPU 时间,数据格式为秒
- TIME: 线程运行总 CPU 时间,数据格式为分:秒
- INTERRUPTED: 线程当前的中断位状态
- DAEMON: 是否是 daemon 线程
- daemon 线程是个服务线程,准确地来说就是服务其他的线程。
- 比如垃圾回收线程,就是最典型的守护线程。
jvm
jvm
可以查看当前 JVM 信息。jvm官方文档
实际实行效果如下(其中把部分value已删除)
|
|
- THREAD 相关
- COUNT: JVM 当前活跃的线程数
- DAEMON-COUNT: JVM 当前活跃的守护线程数
- PEAK-COUNT: 从 JVM 启动开始曾经活着的最大线程数
- STARTED-COUNT: 从 JVM 启动开始总共启动过的线程次数
- DEADLOCK-COUNT: JVM 当前死锁的线程数
- 文件描述符相关
- MAX-FILE-DESCRIPTOR-COUNT:JVM 进程最大可以打开的文件描述符数
- OPEN-FILE-DESCRIPTOR-COUNT:JVM 当前打开的文件描述符数
thread
使用thread ${ID}
会打印线程 ID 的栈,类似下图。thread官方文档
class/classloader相关
sc & sm
sc
- 查看 JVM 已加载的类信息- “Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息。
sm
- 查看已加载类的方法信息。- “Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
jad
反编译指定已加载类的源码,可以查看部署在线上服务器的.class文件对应的java源码。该功能基于一个第三方的反编译工具CFR实现。
注: 可以使用reset命令对增强后的class文件还原, 或者shutdown服务端关闭时也会重置所有增强过的类。
jad官方文档:https://arthas.aliyun.com/doc/jad.html
redefine
加载外部的.class文件替换掉当前的类。类似于热加载或热修复的功能, 修改java文件编译后的, 将替换掉jvm已加载的.class类, 基于jdk本身的限制, 修改的class文件里不允许新增加成员变量和方法
基于这个功能可以模拟一个简单的监控功能, 比如在java文件的某个方法里加上调用耗时和请求参数的打印功能, 然后使用redefine即可看到该方法的耗时时间和参数值, 并且不用重启服务
redefine -p D:/XOrderDetailLoungeMapper.class
注意: 使用时需谨慎,确保修改的代码能正确编译并且逻辑正确,毕竟这个命令的杀伤力太大,验证过之后最好通过 reset 命令还原回来。
redefine官方文档:https://arthas.aliyun.com/doc/redefine.html
monitor/watch/trace 相关
watch
观察指定方法的调用情况, 包括返回值、异常、入参、对象属性值。 格式: watch 类名 方法名 [params:入参, target:当前对象, returnObj:返回值] -x 表示展开或遍历的深度,可以调整来打印具体的参数和结果内容
watch官方文档:https://arthas.aliyun.com/doc/watch.html
trace
方法内部调用路径,并输出方法路径上的每个节点上耗时。
主要用于统计整个调用链路上的所有性能开销和追踪调用链路,使用下来感觉这个命令也是很有用的,包括本地环境, 尤其是要排查接口响应时间慢这样的场景下,可以快速定位到具体哪个方法或哪些方法导致的,甚至包括第三方的方法.
trace官方文档:https://arthas.aliyun.com/doc/trace.html
stack
输出当前方法被调用的路径。
stack官方文档:https://arthas.aliyun.com/doc/stack.html
tt
time tunnel 时间轴, 记录下指定方法每次调用的入参和返回信息。
tt官方文档:https://arthas.aliyun.com/doc/tt.html
profiler/火焰图
profiler
可能是arthas中使用的最多的一个命令了,arthas里的火焰图也是集成的开源项目 async-profiler。
火焰图可以直观的分析代码占用cpu, 内存, 锁争用的情况, 比较直观, 但是这个命令只支持Linux和Mac系统, 一般可以用于线上环境的诊断
说明: 纵轴表示栈帧深度, 横轴表示代码占用CPU/内存的比例, 点击可以查看详情。
|
|
以上操作是将profiler启动后,再手动停止。也可以定时生成一定时间内的火焰图。以下指令生成了开始采样后10秒内的火焰图。
|
|
生成的文件目录
|
|
profiler官方文档:https://arthas.aliyun.com/doc/profiler.html
mat
MAT
(全名:Memory Analyzer Tool
),是一款快速便捷且功能强大丰富的 JVM 堆内存 离线 分析工具。通过展现 JVM 异常时所记录的运行时堆转储快照(Heap dump)状态(正常运行时也可以做堆转储分析),帮助定位内存泄漏问题或优化大内存消耗逻辑。
官网地址:https://www.eclipse.org/mat/
官方说明文档:https://help.eclipse.org/2020-09/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
MAT 的工作原理是对 dump 文件建立多种索引,并基于索引来实现以下四大核心功能,在这些功能的基础上再通过可视化展现辅助 Developer 精细化了解 JVM 堆内存全貌。
- 内存分布
- 对象间依赖(如实体对象引用关系、线程引用关系、ClassLoader引用关系等)
- 对象状态(内存占用量、字段属性值等)
- 条件检索(OQL、正则匹配查询等)
应用场景
- 内存溢出,JVM堆区或方法区放不下存活及待申请的对象。
- 如:高峰期系统出现 OOM(Out of Memory)异常,需定位内存瓶颈点来指导优化。
- 内存泄漏,不会再使用的对象无法被垃圾回收器回收。
- 如:系统运行一段时间后出现
Full GC
,甚至周期性OOM
后需人工重启解决。
- 如:系统运行一段时间后出现
- 内存占用高。
- 如:系统频繁
GC
,需定位影响服务实时性、稳定性、吞吐能力的原因。
- 如:系统频繁
使用介绍
Overview
堆内存大小、对象个数、类的个数、类加载器的个数、GC root 个数、线程概况等全局统计信息。主要包括以下一个主要模块。
全局概览信息
- 堆内存大小、类数量、实例数量、Class Loader数量。
Unreachable Object Histogram
- 展现转储快照时可被回收的对象信息
- 一般不需要关注,除非 GC 频繁影响实时性的场景分析才用到
Biggest Objects by Retained Size
- 展现经过统计过的哪几个实例所关联的对象占内存总和较高,以及具体占用的内存大小,一般相关代码比较简单情况下,往往可以直接分析具体的引用关系异常,如内存泄漏等。
- 此外也包含了最大对象和链接支持继续深入分析。
Object View
通过mat页面左侧的 inspector
面板可以查看对象的属性信息、类继承关系信息等数据,协助分析内存占用高与业务逻辑的关系。
集合状态的检测,如:通过 ArrayList 或数组的填充率定位空集合空数组造成的内存浪费、通过 HashMap 冲突率判定 hash 策略是否合理等。
Actions
histogram
罗列每个类实例的内存占比,包括自身内存占用量(Shallow Heap)及支配对象的内存占用量(Retain Heap),支持按 package、class loader、super class、class 聚类统计,最常用的功能之一。
dominator-tree
按对象的 Retain Heap 排序,也支持按多个维度聚类统计,并提供详细的支配关系,结合 references 可以实现大对象快速关联分析,最常用的功能之一。
top-consumer
展现哪些类、哪些 class loader、哪些 package 占用最高比例的内存。
duplicate-classes
Reports
leak-suspects
直击引用链条上占用内存较多的可疑对象,可解决一些基础问题,但复杂的问题往往帮助有限。
top-components
使用技巧
索引构建
mat很多时候启动会很慢,慢的原因就是mat为提升检索速率,需要在初始化时构建索引index
文件。这一点当dump文件特别大的时候尤其明显,所以建议如果有可能的话,一开始可以将dump文件放在一台内存和CPU都相对较高的机器上完成初始化的索引构建,再将类似下图的文件全部拷贝到用于分析的机器上。这样可以大大提升分析效率。
条件搜索
如下图,可以在搜索框中输入需要查找的类名或关键字,内存分布及对象间依赖的众多功能,均支持按字符串检索、按正则检索等操作。
除以上输入框的搜索外,mat还支持OQL
、虚拟内存地址寻址
。
- OQL,提供一种类似于SQL的对象(类)级别统一结构化查询语言。
- 查找 size=0 且未使用过的 ArrayList。
select * from java.util.ArrayList where size=0 and modCount=0
;
- 查找所有的String的length属性的
select s.length from instanceof String s
- 查找 size=0 且未使用过的 ArrayList。
- 虚拟内存地址寻址
- 根据对象的十六进制地址查找对象
- 根据对象的十六进制地址查找对象
References
提供对象的外部引用关系、被引用关系。通过任一对象的直接引用及间接引用详情(主要是属性值及内存占用),进而提供完善的依赖链路详情。通过outgoing references
、incoming references
可以直击对象间依赖关系,MAT 也提供了链式快速操作。
-
outgoing references
- 对象引用的外部对象(注意不包含对象的基本类型属性。基本属性内容可在 inspector 查看)。
-
incoming references
- 直接引用了当前对象的对象,每个对象的 incoming references 可能有 0 到多个。
-
外部引用关系:
- with outgoing references
- with incoming references
-
被引用关系:
- by outgoing references
- by incoming references
Path To GC Roots
Path To GC Roots
:提供任一对象到GC Root的链路详情,帮助了解不能被 GC 回收的原因。
GC Root
:GC Root 代表通过可达性分析来判定 JVM 对象是否存活的起始集合。JVM 采用追踪式垃圾回收(Tracing GC)模式,从所有 GC Roots 出发通过引用关系可以关联的对象就是存活的(且不可回收),其余的不可达的对象(Unreachable object:如果无法从 GC Root 找到一条引用路径能到达某对象,则该对象为Unreachable object)可以回收。
JProfiler
JProfiler
是一款同时支持离线和在线的性能分析工具,需要注意JProfiler
是收费的应用,需要激活。这里简单介绍下相关的使用情况。
打开hprof文件,进入classes视图,可以查看instance Count和Size等基本信息,大部分情况下占用多的都是byte[]
或long[]
等,需要根据具体的业务分析具体对应的class。
allocations
视图可以查看具体的分配情况,但是本次dump的格式为.hprof
,JProfiler
不支持读取。
在biggest-objects
视图中可以查看当前堆中较大的对象。
选中需要分析的大对象后,可以右击选中show in graph
。
在graph
视图中可以查看类之间的应用关系。
在thread dump
视图中可以查看具体的线程堆。
先前介绍的jmc
、visualVM
、arthas
等基本已经包含了JProfiler
的大部分功能,且不收费,因此对于JProfiler
只是简单介绍下,就不再做更深入的分析。有兴趣的同学,可以自己尝试使用下。
Native memory analysis
本小节介绍下关于堆外内存的一些分析工具。有一点可以明确,堆外内存是由操作系统负责维护和分配的,这也就是为什么下文中会涉及到部分Linux等操作系统的命令。
pmap
使用pmap
可以查看进程内存信息。pmap
是report memory map of a process
的缩写,也就是查看进程的内存映像信息。
pmap
命令用于报告进程的内存映射关系,是Linux调试及运维一个很好的工具。
|
|
gperftools
gperftools
是由google
出品的一个性能分析工具。官网地址:https://github.com/gperftools/gperftools
我们见过的最快的malloc;尤其适用于线程和 STL。另外:线程友好的堆检查器、堆分析器和 cpu 分析器。
gperftools
是一个高性能多线程malloc()
实现的集合,以及一些非常漂亮的性能分析工具。gperftools
是根据 BSD 许可证的条款分发的。
- install gcc
|
|
- install libunwind
|
|
- install gperftools
|
|
- modify setting
|
|
- dump profile
|
|
- Cpu Profiler:
|
|
gdb
pmap
找到内存申请的地址块,可以使用gdb attach
后dump
某一段的内存地址,后查看内存的数据。
注意: gdb attach
进程会暂停,线上谨慎操作。
- 安装gdb
sudo yum install -y gdb
- attach
sudo -u deploy gdb --pid={pid}
- dump内存
dump memory {path} {start_address} {end_address}
- 内存地址可以使用pmap或less /proc/{pid}/maps查看
- 查看内存中的字面量:
strings {path}
NMT
Native Memory Tracking (NMT)
堆外内存跟踪,是Hotspot VM用来分析VM内部内存使用情况的一个功能。我们可以利用上文提到的jcmd
工具来访问NMT的数据。官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html
NMT
必须先通过VM启动参数-XX:NativeMemoryTracking
打开,打开NMT会带来5%-10%的性能损耗。还有点需要注意,NMT
能够分析JVM自身分配的一些内存,但是无法追踪非JVM分配的内存,例如JNI等native code,
|
|
- off
- 默认关闭
- summary
- 只统计各个分类的内存使用情况.
- detail
- Collect memory usage by individual call sites.
开启NMT后,运行进程,可以使用下面的jcmd
命令查看直接内存:
|
|
- summary
- 分类内存使用情况.
- detail
- 详细内存使用情况,除了summary信息之外还包含了虚拟内存使用情况。
- baseline
- 创建内存使用快照,方便和后面做对比
- summary.diff
- 和上一次baseline的summary对比
- detail.diff
- 和上一次baseline的detail对比
- shutdown
- 关闭NMT
总结
本文所阐述的是实际开发中经常会使用到的一些Java性能监控与排障处理工具,可能在不远的将来会有更加优秀的分析工具面世。
但是,无论用什么工具,最终一定会定位到具体的业务代码或者框架的代码,再加上对于业务和技术的理解相互配合才能准确的定位到问题,单单为了使用工具而来学习工具,这样做的意义不大。
只有多多分析问题,排查问题才能对发挥出各种工具应有的价值。
重要的事情说三遍:实践、实践、实践。
参考文献
- JVM——jmc使用说明
- JVM 内存分析工具 MAT 的深度讲解与实践——入门篇
- 性能诊断利器 JProfiler 快速入门和最佳实践
- 深入浅出JProfiler
- Introduction To JProfiler
- 【原创】JVM的Heap Memory和Native Memory
- What is the difference between off-heap, native heap, direct memory and native memory?
- 一次完整的JVM堆外内存泄漏故障排查记录
- Java 内存之直接内存(堆外内存)
- 记一次JVM堆外内存泄露Bug的查找
- JVM源码分析之堆外内存完全解读
- gperftools做堆外内存分析(案例JVM Inflater 内存泄漏分析)
- Troubleshooting Guide for Java SE 6 with HotSpot VM
- google-perftools安装
- gperftools CPU profiler
- Spring Boot引起的“堆外内存泄漏”排查及经验总结