使用Oracle Java Mission Control监控JVM运行状态
Oracle Java Mission Control(以下称JMC)是一个集成到JVM(jdk7u40+)的性能剖析和诊断工具,相比起JProfiler之类的性能剖析工具,JMC更加简单易用,界面友好。
JMC使用了JVM内部特定的基于事件的接口,不同于一般性能剖析工具使用JVMPI/JVMTI方式实现。因此JMC几乎不会给应用造成额外的压力,可以用在负载很高的生产环境中:
- JFR在默认设置continuous下运行几乎对性能没有影响
- JFR在默认设置profiling下运行通常造成小于2%的性能下降
在JDK 7 Update 40及其以后的版本中已经集成了JMC(%JDK_HOME%\bin\jmc.exe),打开此程序,会发现它是一个典型的Eclipse RCP应用。本文简单的介绍如何使用JMC来监控、记录JVM的运行过程。
1 2 3 4 5 6 |
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote -XX:+UnlockCommercialFeatures -XX:+FlightRecorder |
如果要在目标JVM上启动持续不断的黑匣子记录,并在JVM正常退出时Dump记录为文件,可以参考下面的脚本设置JVM参数:
1 2 3 4 5 6 |
for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set md=%%a-%%b-%%c) for /f "tokens=1-3 delims=/:." %%a in ("%TIME%") do (set mt=%%a%%b%%c) set CUR_TIME=%md%-%mt% rem 启用黑匣子默认记录行为。官方文档注明dumponexitpath支持目录,但我在7u71版本下测试失败 set JAVA_OPTS=%JAVA_OPTS% -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=%LOG_DIR%\jfr\dump-on-exit-%CUR_TIME%.jfr,maxage=3600s,settings=%JRE_HOME%\lib\jfr\profile.jfc |
配置并重启目标JVM后,打开JMC,点击 File - Connect... - Create New connection,参考下图设置,完毕后点击Test connection确认可以连通,最后点击Finish结束配置。
在左侧窗格JVM Browser视图中,双击刚刚创建的JMC连接,点击MBean Server,即可在右侧主窗体查看实时的JVM状态信息,供6个选项卡,下面依次描述。
包含JVM概要性的实时图表,包含3个面板:
面板 | 说明 |
仪表盘(Dashboard) |
以仪表方式显示系统运行参数,默认显示堆内存占用、JVM处理器占用、垃圾回收前后的堆内存情况。注意仪表上有有两个指针,灰色的通常显示最大值,黑色的显示当前值。 面板右侧的+号按钮,点击后可以添加目标JVM支持的任何运行参数到仪表盘中 |
处理器(Processor) | 显示JVM、操作系统的动态CPU占用曲线 |
内存(Memory) | 默认显示Java堆内存占用情况,可以选择查看物理内存占用等项目 |
以树形结构显示目标JVM支持的所有MBean列表和属性、操作集。
包含若干预定义的触发器,可以在特定条件下,执行预定义的动作。支持的触发器包括:
触发器 | 说明 |
CPU Usage - JVM Process (Too High) | 当JVM的进程的CPU占用过高时 |
CPU Usage - JVM Process (Too Low) | 当JVM的进程的CPU占用过低时 |
CPU Usage - Machine (Too High) | 当OS的CPU占用过高时 |
CPU Usage - Machine (Too Low) | 当OS的CPU占用过低时 |
Deadlocked Threads | 当发生JVM线程死锁时 |
Live Set (Too Large) | 当垃圾回收之后,存活对象占堆大小百分比过大时 |
Thread Count (Too High) | 当JVM线程总数过大时 |
此外,任何MBean属性均可以作为触发器使用,中间件(例如Tomcat、Weblogic、ActiveMQ)都有自己的扩展。 双击触发器后,右侧即显示规则详情(Rule Details),可以定义触发的阈值(到达时,或者从异常值恢复时)、执行的动作,可用的动作包括:
动作 | 说明 |
Application alert | 在应用程序上显示一个警告对话框 |
Console output | 在控制台(stdout)上输出警告信息 |
Dump Flight Recording | 保存黑匣子记录内容 |
HPROF Dump | 保存堆镜像 |
Log to file | 在日志文件中输出警告信息 |
Send e-mail | 通过电子邮件发送警告信息 |
Start Continuous Flight Recording | 启动不间断的黑匣子记录 |
Start Time Limited Flight Recording | 启动限制时间长度的黑匣子记录 |
所谓LiveSet,是指在一次垃圾回收之后,堆内存的占用比率,取值在0-1之间。
如果GC后,可用的内存空间不足,则LiveSet接近100%,这预示可能存在OOM风险。在生产环境下,我们可以监控LiveSet,并在其过大时,把堆内存映像Dump出来供后续分析。
触发器配置示例:
其中Condition面板:
- Current Value为指标的当前值,当发生GC后,自动刷新
- Max trigger value为触发器激活时的指标值
- Sustained period,指标必须到达Max trigger value至少多长时间,触发器才可能激活
- Limit period,连续两次激活触发器的最小间隔时间
其中Action面板:
- Only live,如果勾选,仅仅GCRoot可达的对象才被Dump出来
- hprof路径为服务器的路径
其中Constraints面板:可以指定此触发器在什么日期、什么时间段启用。
要分析内存映像,你可以使用如下命令启动一个Web UI:
1 |
jhat -port 7770 -J-Xmx4G default.hprof |
显示JVM和系统的概要信息,包含3个面板:
- 系统信息:例如OS类型、硬件架构、系统内存、JVM的PID、JVM版本、Classpath、JVM启动时间、JVM参数(Java Options)
- JVM统计:例如已加载的类数量、已运行时间
- 系统属性:显示JVM的系统属性
显示JVM内存占用情况,分为上下两个面板。
面板 | 说明 |
GC Table |
显示垃圾回收器(ParNew:新生代的并行回收器,ConcurrentMarkSweep:并发垃圾回收器…)的工作情况,包括:GC总计时间、GC总次数、最近GC开始结束时间、GC标识符、GC线程数 |
Active Memory Pools |
显示当前的内存池的大小、已用大小、类型(堆/非堆),可能有以下内存池:
关于垃圾回收器内存分代,参考:JVM参数与性能调优 |
显示JVM线程活动情况,分为三个面板。
面板 | 说明 |
Live Thread Graph |
显示当前存活线程的数量、守护线程的数量、线程数量峰值的实时曲线 |
Live Threads |
显示当前存活线程的列表,可以进行CPU占用分析、死锁检测、线程分配内存量的分析 |
Stack traces for selected threads | 显示瞬时的线程调用栈 |
正如民航的黑匣子一样,JMC的这个功能,可以用于事后分析系统性能低下,甚至崩溃的原因。JMC支持两种方式来启动黑匣子记录:通过触发器自动启动, 或者在左侧面板,双击Flight Recorder,手工启动。手工启动可以参考下图设置:
其中:Time fixed recording表示记录定长时间,时间到达后,自动停止记录;Continuous recording表示进行持续不断的记录,直到JVM退出或者接收到停止指令,这种记录方式下可以随时进行dump操作。
虽然黑匣子在JVM本地记录数据,但不在Java堆中记录,因此它并不会影响内存特性和垃圾收集。
黑匣子录制完成后,会在你选择的目录中生成一个.jfr文件,该文件包含录制期间JVM中发生的各种事件。
黑匣子通过JVM内部API来收集数据,并存放在一个线程本地(thread-local)的缓冲中,并最终刷入全局内存缓冲(global in-memory buffer),内存缓冲中的数据,会被刷入磁盘,磁盘文件时经过压缩的。每个记录数据块,要么在内存中,要么在磁盘中,这意味着——进程被停止或者宕机时,部分数据会丢失,JVM崩溃会也可能导致一部分数据丢失。
可以配置黑匣子不使用磁盘空间,这种场景下,全局内存缓冲被循环使用,旧的数据自动丢弃。
在JMC界面上,点击 Windows - Flight Recording Template Manager,看到类似如下界面。可以导入、导出、编辑JFR的设置文件。
我们可以导入%JDK_HOME%\jre\lib\jfr下的两个自带模板:default.jfc(Continuous)、profile.jfc(Profiling)。这两个模板的对比如下:
模板 | 特性 |
Continuous |
Garbage Collector:正常 未启用记录的事件类型:Exception Throw、Object Alloc In New TLAB、Object Alloc Outside TLAB、Class Loading、Compile/Failure、GC/Object Count、GC/Object Count After GC、GC/Phases/Phase Level 3、Profiling/Execution Sample Info、 |
Profiling |
Garbage Collector:正常 未启用记录的事件类型:Exception Throw、Class Loading、GC/Object Count、GC/Object Count After GC、GC/Phases/Phase Level 3、Profiling/Execution Sample Info、 |
利用诊断命令,可以通过脚本控制黑匣子的行为,命令的示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
rem 获取进程号为5361的JVM关于JFR.start命令的帮助 jcmd 5361 help JFR.start rem 检查进程的JFR运行状态 jcmd 7648 JFR.check rem 输出如下。注意recording=0表示正在运行的记录的标识符,后续操作需要用到这个标识符 rem 7648: rem Recording: recording=0 name="HotSpot default" maxage=1h (running) rem 启动一个15分钟的黑匣子记录,延迟5分钟启动,使用JDK自带的默认设置(default.jfc),保存Dump文件到D盘 jcmd 7648 JFR.start name=SampleRcd delay=300 duration=900s filename=D:\temp\SampleRcd.jfr \ settings=D:\JavaEE\jdk\x64\jre\lib\jfr\default.jfc rem 相对路径取决于目标JVM jcmd 1234 JFR.start delay=0s duration=${jfr_du}s name=$name filename=$PWD/$name.jfr settings=profile rem 下面的脚本,可以用于定期进行记录,按记录时间保存为不同的文件 setlocal set DUMP_DIR=D:\temp set SERVICE_NAME=Tomcat7 set JAVA_HOME=D:\JavaEE\jdk\x64\1.7 pushd %JAVA_HOME%\bin for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set md=%%a-%%b-%%c) for /f "tokens=1-3 delims=/:." %%a in ("%TIME%") do (set mt=%%a%%b%%c) set CUR_TIME=%md%-%mt% for /F "tokens=1,3" %%a in ('sc queryex %SERVICE_NAME%') do ( if "%%a" == "PID" set "JPID=%%b" ) rem 启动飞行记录,如果目标JVM是以SYSTEM身份启动的NT Service,可以使用PSTools发起jcmd命令,否则会报错:insufficient memory or insufficient privileges to attach rem psexec -s jcmd %JPID% JFR.start ... jcmd %JPID% JFR.start name=rcd-%CUR_TIME% duration=900s compress=true filename=%DUMP_DIR%\rcd-%CUR_TIME%.jfr settings=%JAVA_HOME%\jre\lib\jfr\profile.jfc popd endlocal rem 导出JFR的Dump文件 rem 如果目标JVM正在在运行持续不断的黑匣子记录,使用该命令,再结合定时任务机制,可以定期保存黑匣子记录的Dump文件 jcmd 7648 JFR.dump recording=0 filename=F:\temp\dump.jfr |
命令 | 说明 |
JFR.start |
启动一个JFR(黑匣子记录)实例,参数: name 本次记录的名称 |
JFR.check |
显示运行中的JFR的状态,参数: name 记录名称 |
JFR.stop |
停止运行中的JFR,参数: name 记录名称 |
JFR.dump |
将录制中的JFR保存到文件,参数: name 记录名称 |
打开黑匣子记录的.jfr文件,即可看到类似下面的界面:左侧是记录的不同视图,可以点击切换;右侧主面板,顶部是事件的时间轴,拖动滑块,可以改变聚焦的时间范围(勾选右边的同步选择可以在切换视图时保持聚集范围),底部有若干选项卡。
下面,按照视图分别描述。
包含4个选项卡:
可以看到聚焦时间范围内的实时仪表(堆占用、CPU占用、GC停顿时间),以及CPU、堆使用情况的曲线图。
可以看到JVM的一些基本信息,包括启动时间、JVM参数等。
可以看到JVM的系统属性列表
可以显示记录的事件的列表
包含6个选项卡
Memory Usage:可以显示聚焦时间范围内OS总内存、OS已使用内存、提交的堆、使用的堆的大小曲线图。
GC Configuration:显示简单的GC配置
GC Statistics:显示简单的GC统计,包括垃圾回收次数、停顿时间
上面的面板,包含4个选项卡:
Heap:堆的使用情况曲线,红色小柱形显示了GC停顿的持续时间(刻度在右边)
Reference Objects:包含软引用、弱引用、幻引用、一般引用的数量变化区域图
Failed Promotions:失败的晋升列表,所谓晋升是指:年轻代对象向年老代转移的GC动作,包含对象个数和大小等信息
Failed Evacuations:
下面的面板,左侧显示历次GC的列表,右侧显示单次GC的详细信息,如下:
General:显示GC类型、触发原因、ID、开始结束时间、暂停时间
GC Phase:GC每个步骤的开始和持续时间
Reference Objects:本次GC涉及的各类引用的数量
Heap:堆在GC前后的大小对比
Perm Gen:永久代在GC前后的大小对比
显示各代、各次GC所消耗的时间
显示GC的详细配置,包括GC基本配置、堆的配置、年轻代的配置
显示聚焦时间范围内的内存分配统计信息
General:在线程本地分配缓冲(TLAB)内、外(即Eden区域)中分配的对象个数、内存大小。面板下部是内存分配大小按时间的柱形图
Allocation in new TLAB:在TLAB中分配的内存,按对象类型、或者按执行分配的线程的占比饼图;Allocation Profile可以剖析分配内存热点所在的调用栈
Allocation outside TLAB:在Eden区域的内存分配情况,包含的子选项卡同上
示例:
在Allocation By Class选项卡中:
- 上面板Allocation Pressure,按对象类型,显示内存占用最大的对象
- 下面板Stack Trace,选中上面板的某个对象类型后,在此显示分配对象的调用栈。第一行为直接分配对象的代码
显示对象数量和内存占用的统计信息,包含上下两部分
上面的面板:显示占有堆内存百分比大于0.5%的类型的列表
下面的面板:显示增长数量最快的类型的列表
包含6个选项卡:
显示占用CPU时间最多的代码,分为上下两部分
上面的面板:显示CPU时间占用最多的包,包括列表和饼图
下面的面板:显示CPU占用最多的类,按占比降序排列
显示热点方法(CPU占用时间最多),按占比降序排列,点击左侧小箭头可以向下钻取(找到哪些代码调用了这个方法导致的热点)
类似于热点方法选项卡,但是会显示所有热点方法的完整调用栈
包含3个选项卡,显示异常(包括错误)的统计信息
Overview:显示异常和错误的总数、最频繁发生异常的调用栈
Exceptions、Error:单独显示异常或者错误的总数、频繁调用栈,注意,异常默认是不记录的
显示即时编译的次数、编译大小、失败的日志
显示按时间的类加载、卸载的数量曲线图
分为上下两部分:
上面的面板:显示OS、JVM的CPU占用率的按时间统计的区域图
下面的面板:显示守护线程、活动线程数量的曲线图
显示CPU占用最多的线程,及选中线程的热点方法,分为上下两部分
上面的面板:显示CPU占用最多的线程,按占比降序排列
下面的面板:显示上面板选中线程的热点方法、调用栈
显示因为锁争用导致的线程阻塞的统计信息,分为3个选项卡:
Top Blocking Locks:发生阻塞最多的监视器(锁)对象的列表,包含了锁定次数、最长时间
Top Blocked Threads:因为等待锁被阻塞时间最长的线程的列表
Top Blocking Threads:因为占用锁导致其他线程等待时间最长的线程的列表
显示因为:Java线程休眠、监视器等待、Socket读取、监视器阻塞等原因导致的线程执行延迟的情况,包括延迟信息的列表和饼图,已经导致延迟的线程调用栈。延迟相关的事件包括:Java Thread Park(Waiting)、Java Monitor Wait(等待被唤醒)、Java Monitor Blocked(积极进入同步区被阻塞)等。
包含定时捕获的JVM中所有线程的瞬时调用栈镜像
Lock Instances:监视器对象的列表,包含锁定次数、总计持续时间
Trace Tree:选中监视器对象的锁定调用栈,每个调用栈锁定的次数
Top Threads:占用监视器次数最多的线程列表
分为两个选项卡
File Overview:
File Reads and Writes:显示按时间读写文件字节数的曲线
File Read、File Write:显示针对具体文件的读写字节数、次数的列表
Socket Overview:
Socket Read and Writes:显示按时间读写Socket字节数的曲线
Socket Read、Socket Writes:显示针对具体目标主机、端口读写字节数、次数的列表
按文件、线程、事件统计文件读操作的列表,以及每个文件读取按时间的变化、线程调用栈
按文件、线程、事件统计文件写操作的列表,以及每个文件写入按时间的变化、线程调用栈
按目标主机、线程、事件统计文Socket读操作的列表,以及每个Socket读取按时间的变化、线程调用栈
按目标主机、线程、事件统计文Socket写操作的列表,以及每个Socket写入按时间的变化、线程调用栈
显示黑匣子记录的事件的相关信息,左侧面板Event Types可以过滤关注的事件类型。总体来说,事件可以分为三类:持续事件(duration,持续一段时间,例如GC事件);瞬时事件(instant,立即发生和结束,例如内存溢出);样本事件(sample,通过定期采样获取,例如栈dump)。
Producers:显示事件生产者的列表,以及制造事件的占比饼图
Event Types:显示各类型事件持续总时间、次数,占比饼图
显示每一个事件的记录,按时间排列,为每个事件显示:开始时间、结束时间、持续时间、产生事件的线程
以时间轴的形式展示事件历史
以列表形式展示各线程产生事件的数量、持续时间
按产生事件持续时间长短,降序排列相关的调用栈
可以按单个事件类型进行简单的统计分析,支持不同的分组方式,支持总数、平均数、次数等指标,选中单个统计结果,可以显示调用栈。
Leave a Reply