一、JDK命令行工具
JDK命令行工具位于JDK的bin目录下,能在处理应用程序性能问题、定位故障时发挥很大的作用。
这些工具都比较小,因为这些命令行工具大多数是jdk\lib\tools.jar类库的一层简单包装而已。
注:tools.jar中的类库不属于Java的标准API;
如果需要监控运行于JDK 1.5的虚拟机之上的程序,在程序启动时要添加参数“-Dcom.sun.management.jmxremote”开启JMX管理功能,如果被监控程序运行于JDK1.6的虚拟机之上,那JMX管理默认是开启的,不需要在虚拟机启动时添加任何参数。
下图列举了JDK主要命令行监控工具及用途:
1、jps:虚拟机进程状况工具
功能:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class, main()函数所在的类)的名称,以及这些进程的本地虚拟机的唯一ID(LVMID, Local Virtual Machine Identifier)。
注:对于本地虚拟器进程来说,LVMID与操作系统的进程ID(PID, Process Identifier)是一致的。
jps命令格式:
jps [options] [hostid]
主要选项:
-q :只输出LVMID, 省略主类的名称
-m : 输出虚拟机进程启动时传递给主类main()函数的参数
-l :输出主类的全名,如果进程执行的是Jar包,输出Jar路径
-v : 输出虚拟机进程启动时JVM参数
示例:
CMD进入Java的安装目录下的bin目录,输入:jps -l,会输出LVMID 和主类的全名。类似下面这样:
6536
6380 gui.Button1
6188 sun.tools.jps.Jps
2、jstat:虚拟机统计信息监视工具
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在只有控制台环境时,它将是运行期定位虚拟机性能问题的首选工具。
jstat命令格式:
jstat [ option vmid [interval [s | ms] [count]] ]
注:其中VMID:如果是本地虚拟机进程,VMID与LVMID是一致的,如果是远程虚拟机进程,那么VMID的格式应当是:
[protocol:] [//] lvid [@hostname[:port] /servername]
参数interval和count代表查询间隔和次数。如果省略这两个参数,说明只查询一次,假设需要每250毫秒查询一次进程2764垃圾收集的状况,一共查询20次,那命令应当是:
jstat -gc 2764 250 20
option选项:该选项代表用户希望查询的虚拟机信息,主要分为3类:类装载、垃圾收集和运行期编译状态。具体选项及作用如下:
3、jinfo :Java 配置信息工具
jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机的各项参数。
注:JDK1.6中,jinfo对于Windows平台的功能仍然有较大的限制,只提供了最基本的 -flag选项。
jinfo命令格式:jinfo [ option ] pid
例如:查询CMSInitiatingOccupancyFraction参数值:jinfo -flag CMSInitiatingOccupancyFraction 8088
4、jmap : Java内存映像工具
jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)。
如果不使用jmap命令,可以通过指定虚拟机参数来获得dump文件,例如:指定-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用【Ctrl】+ 【Break】键让虚拟机生成dump文件。或者Linux下通过Kill -3命令。
jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列,Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。
与jinfo命令一样,jmap在windows平台下有些功能是受限的,可以使用的是-dump选项来获取dump文件、-histo选项用来查看每个类的实例、空间占用统计等。
jmap命令格式:
jmap [ option ] vmid
option选项:
5、jhat:虚拟机堆转储快照分析工具(dump分析工具)
通过jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器中查看。
jhat工具很少使用,因为有比它强大的专业分析dump的工具,比如VisualVM、Eclipse Memory Analyzer、IBM HeapAnalyzer等工具。
6、jstack : Java 堆栈跟踪工具
jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因。
jstack命令格式:
jstack [ option ] vmid
option选项的定义如下:
二、JDK可视化工具
1、JConsole :Java监视与管理控制台
JConsole(Java Monitoring and Management Console)是一款基于JMX的可视化监视和管理的工具。
2、VisualVM:多合一故障处理工具
VisualVM(All-in-One Java Troubleshooting Tool)是目前随JDK发布的功能最强大的运行监控和故障处理程序。
它的一个重要优点是:不需要被监视的程序基于特殊Agent运行,因此它对应用程序的实际性能的影响很小,使得它可以直接应用在生产环境中 。
它需要装扩展插件才能发挥它的功能,插件都以.nbm为后缀的包,安装插件:点击“工具”-> "插件"-> "已下载"菜单。
以下是插件的介绍:
** BTrace:动态日志跟踪
它的作用是在不停止目标程序运行的前提下,通过HotSpot虚拟机的HotSwap技术动态加入原本并不存在的调试代码,这可以在不停止部署的程序的条件下调试问题。
BTrace的用法还有很多,打印调用堆栈、参数、返回值只是最基本的应用,在它的网站上有使用BTrace进行性能监视、定位连接泄漏、内存泄漏、解决多线程竞争问题等的使用例子。
三、故障处理
1、案例一:高性能硬件上的程序部署策略
在高性能硬件上部署程序,目前主要有两种方式:
** 通过64位JDK来使用大内存
** 使用若干个32位虚拟机建立逻辑集群来利用硬件资源
通过第一种方式,即64位JDK来管理大内存,需要考虑下面可能的问题:
** 内存回收导致的长时间停顿;(一次Full GC会花费较长时间)
** 现阶段,64位JDK的性能测试结果普遍低于32位JDK
** 需要保证程序足够稳定,因为这种应用要是产生堆溢出几乎就无法产生堆转储快照(十几GB的dump文件),即使产生了也无法分析;
** 相同的程序在64位JDK中消耗的内存一般比32位JDK大,这是由于指针膨胀及数据类型对其补白等因素导致的。
通过第二种方式,具体做法是在一台物理机器上启动多个应用服务器进程,给每个服务器进程分配不同的端口,然后在前端搭建一个负载均衡器,以反向代理的方式来分配访问请求。这种方式需要考虑的问题是:
** 尽量避免节点竞争全局的资源,最典型的就是磁盘竞争
** 很难最高效率地利用某些资源池,譬如连接池
** 各个节点仍然不可避免地受到32位的内存限制
** 大量使用本地缓存的应用,在逻辑集群中会造成较大的内存浪费,因为每个逻辑节点上都有一份缓存,可以考虑把本地缓存改为集中式缓存。
2、集群间同步导致的内存溢出
3、堆外内存导致的溢出错误
关于Direct Memory的垃圾回收:垃圾收集进行时,虚拟机会对Dircet Memory进行回收,但是它不会在发现Dircet Memory空间不足时就通知收集器进行垃圾回收,它只能等待老年代满了后Full GC,然后“顺便地”帮它清理掉内存的废弃对象。或者等到抛出内存溢出异常时,先chatch掉,再在catch块里面进行System.gc()。如果此时虚拟机打开了-XX:+DisableExplicitGC开关,那么仍然是不能清理Dircet Memory空间的。
从实践角度出发,除了Java堆和永久代之外,我们要注意以下区域也可能占用较多的内存:这里所有的内存总和会受到操作系统进程最大内存的限制:
** 、Direct Memory: 可通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。
**、线程堆栈:可通过-Xss调整大小,内存不足时抛出*Error(纵向无法分配,即无法分配新的栈帧)或OutOfMemoryError:unable to create new native thread(横向无法分配,即无法建立新的线程)。
**、Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB的内存,连续多的话这块内存占用也比较可观。如果无法分配,则可能会抛出IOException:Too many open files异常。
**、JNI代码:如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中。
**、虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。
4、外部命令导致系统缓慢
每个用户请求的处理都需要执行一个外部shell脚本来获取系统的一些信息。执行这个shell脚本是通过Java的Runtime.getRuntime().exec()方法来调用的,这种调用方式可以达到目的,但是它在Java虚拟机中非常消耗资源,即使外部命令本身能很快执行完毕,频繁调用时创建进程的开销也非常可观。
原因:Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。频繁执行此过程,系统消耗会很大,不仅是CPU,内存的负担也很重。
解决方法:去掉shell脚本执行的语句,改为使用Java的API去获取系统信息。
5、服务器JVM进程崩溃
四、调优实战