Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。
4.1 概述
经过前面两章对于虚拟机内存分配与回收技术各方面的介绍,相信读者已经建立了一个比较系统、完整的理论基础。理论总是作为指导实践的工具,把这些知识应用到实际工作中才是我们的最终目的。
接下来的两章,我们将从实践的角度去认识虚拟机内存管理的世界。给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。这里说的数据包括但不限于异常堆栈、虚拟机运行日志、垃圾收集器日志、线程快照(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等。恰当地使用虚拟机故障处理、分析的工具可以提升我们分析数据、定位并解决问题的效率,但我们在学习工具前,也应当意识到工具永远都是知识技能的一层包装,没有什么工具是“秘密武器”,拥有了就能“包治百病”。
4.2 基础故障处理工具
Java开发人员肯定都知道JDK的bin目录中有java.exe、javac.exe这两个命令行工具,但并非所有程序员都了解过JDK的bin目录下其他各种小工具的作用。随着JDK版本的更迭,这些小工具的数量和功能也在不知不觉地增加与增强。除了编译和运行Java程序外,打包、部署、签名、调试、监控、运维等各种场景都可能会用到它们,这些工具如图4-1所示。
在本章,笔者将介绍这些工具中的一部分,主要是用于监视虚拟机运行状态和进行故障处理的工具。这些故障处理工具并不单纯是被Oracle公司作为“礼物”附赠给JDK的使用者,根据软件可用性和授权的不同,可以把它们划分成三类:
- 商业授权工具:主要是JMC(Java Mission Control)及它要使用到的JFR(JavaFlight Recorder),JMC这个原本来自于JRockit的运维监控套件从JDK 7 Update40开始就被集成到OracleJDK中,JDK 11之前都无须独立下载,但是在商业环境中使用它则是要付费的。
- 正式支持工具:这一类工具属于被长期支持的工具,不同平台、不同版本的JDK之间,这类工具可能会略有差异,但是不会出现某一个工具突然消失的情况。
- 实验性工具:这一类工具在它们的使用说明中被声明为“没有技术支持,并且是实验性质的”(Unsupported and Experimental)产品,日后可能会转正,也可能会在某个JDK版本中无声无息地消失。但事实上它们通常都非常稳定而且功能强大,也能在处理应用程序性能问题、定位故障时发挥很大的作用。
读者如果比较细心的话,还可能会注意到这些工具程序大多数体积都异常小。假如之前没注意到,现在不妨再看看图4-1中的最后一列“大小”,各个工具的体积基本上都稳定在21KB左右。并非JDK开发团队刻意把它们制作得如此精炼、统一,而是因为这些命令行工具大多仅是一层薄包装而已,真正的功能代码是实现在JDK的工具类库中的,读者把图4-1和图4-2两张图片对比一下就可以看得很清楚。假如读者使用的是Linux版本的JDK,还可以发现这些工具中不少是由Shell脚本直接写成,可以用文本编辑器打开并编辑修改它们。
JDK开发团队选择采用Java语言本身来实现这些故障处理工具是有特别用意的:当应用程序部署到生产环境后,无论是人工物理接触到服务器还是远程Telnet到服务器上都可能会受到限制。借助这些工具类库里面的接口和实现代码,开发者可以选择直接在应用程序中提供功能强大的监控分析功能。
本章所讲解的工具大多基于Windows平台下的JDK进行演示,如果读者选用的JDK版本、操作系统不同,那么工具不仅可能数量上有所差别,同一个工具所支持的功能范围和效果都可能会不一样。本章提及的工具,如无特别说明,是JDK 5中就已经存在的,但为了避免运行环境带来的差异和兼容性问题,建议读者使用更高版本的JDK来验证本章介绍的内容。通常高版本JDK的工具有可能向下兼容运行于低版本JDK的虚拟机上的程序,反之则一般不行。
注意 如果读者在工作中需要监控运行于JDK 5的虚拟机之上的程序,在程序启动时请添加参数“-Dcom.sun.management.jmxremote”开启JMX管理功能,否则由于大部分工具都是基于或者要用到JMX(包括下一节的可视化工具),它们都将无法使用,如果被监控程序运行于JDK 6或以上版本的虚拟机之上,那JMX管理默认是开启的,虚拟机启动时无须再添加任何参数。
4.2.1 jps:虚拟机进程状况工具
JDK的很多小工具的名字都参考了UNIX命令的命名方式,jps(JVM ProcessStatus Tool)是其中的典型。除了名字像UNIX的ps命令之外,它的功能也和ps命令类似:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(LVMID,LocalVirtual Machine Identifier)。虽然功能比较单一,但它绝对是使用频率最高的JDK命令行工具,因为其他的JDK工具大多需要输入它查询到的LVMID来确定要监控的是哪一个虚拟机进程。对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID,Process Identifier)是一致的,使用Windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID,但如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就必须依赖jps命令显示主类的功能才能区分了。
jps命令格式:
jps [ options ] [ hostid ]
jps执行样例:
jps -l
1168 jdk.jcmd/sun.tools.jps.Jps
jps还可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,参数hostid为RMI注册表中注册的主机名。jps的其他常用选项见表4-1。
4.2.2 jstat:虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有GUI图形界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工具。
jstat命令格式为:
jstat -<option> <vmid> [<interval> [<count>]]
对于命令格式中的VMID与LVMID需要特别说明一下:如果是本地虚拟机进程,VMID与LVMID是一致的;如果是远程虚拟机进程,那VMID的格式应当是:
[protocol:][//]lvmid[@hostname[:port].servername]
参数interval和count代表查询间隔和次数,如果省略这2个参数,说明只查询一次。假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:
jstat -gc 2764 250 20
选项option代表用户希望查询的虚拟机信息,主要分为三类:类加载、垃圾收集、运行期编译状况。详细请参考表4-2中的描述。
jstat监视选项众多,囿于版面原因无法逐一演示,这里仅举一个在命令行下监视一台服务器的内存状况的例子,用以演示如何查看监视结果。监视参数与输出结果如代码清单4-1所示。
jstat -gcutil 6352
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
0.08 0.00 52.00