1. 介绍

“纸上得来终觉浅,绝知此事要躬行。”最近发现自己在JVM调优工具的使用上还存在知识盲区,故尽快调研了一些资料,总结一下这些工具的使用,以供以后自己复习查看。

JDK提供的小巧工具简单介绍一览如下:

名称 作用
jps JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
jstat JVM Statistics Monitoring Tool,显示收集HptSpot虚拟机各方面的运行数据
jinfo Configuration Info For java,显示虚拟机配置信息
jmap Memory Map for Java,生成虚拟机的内存转储快照(heapdump)
jhat Stack Trace for Java显示虚拟机的线程快照

2. GC日志

首先需要在启动JVM时候加上参数来打开GC日志。GC日志参数说明如下:
期中-verbose.gc也可以写成-XX:+PrintGC

注意下图中有些地方大小写错误了,例如第二行的-XX需要大写,请知悉

2.1 -XX:+printGC查看简单的GC日志

参数-XX:+PrintGC(或者-verbose:gc)开启了简单GC日志模式,为每一次新生代(young generation)的GC和每一次的Full GC打印一行信息。

下面来实际使用下:
在IDEA中设置相关的VM参数

然后启动一个项目。我这里启动的是阿里的一个开源项目otter,可以看到程序运行过程中,每次发生full GC或者minor GC都会有相关的信息输出


每行开始首先是GC的类型(可以是“GC”或者“Full GC”),然后是在GC之前和GC之后已使用的堆空间,再然后是当前的堆容量,最后是GC持续的时间(以秒计)。

简单模式的GC日志格式是与GC算法无关的,日志也没有提供太多的信息。在上面的例子中,我们甚至无法从日志中判断是否GC将一些对象从young generation移到了old generation。所以详细模式的GC日志更有用一些。

2.2 -XX:PrintGCDetails


如果不是使用-XX:+PrintGC,而是-XX:PrintGCDetails,就开启了详细GC日志模式。在这种模式下,日志格式和所使用的GC算法有关。

从输出的GC详细日志来看,我们可以看到使用的垃圾回收器的名字,比如PSYoungGen、ParOldGen。我们使用的是JDK1.8以上的版本,所以看到并没有PermGen,只有元数据区了。此外还可以看到[PSYoungGen ....]后面的内容是堆的大小。最后时间上如果user时间多与real时间,则说明采用了多线程。

常见的垃圾收集器有:

  1. Throughput垃圾收集器
  2. Serial垃圾收集器
  3. CMS垃圾收集器

从我们运行的例子来看,JDK1.8在年轻代默认使用的垃圾收集器是:吞吐量垃圾收集器,也叫ParallelScanvenge GC;在老年代使用的收集器是Parallel Old垃圾收集器。这两个都属于并行的垃圾收集器。PS GC采用复制的GC算法,Par Old采用标记整理算法(因为老年代避免复制存活率较高的对象)

值得一提的是,PS GC和ParNew都是并发GC,但是关注点不同,而且PS GC没有按照老的GC分代框架来开发,是一个牛人没按照原有GC框架自己硬开发出来的。在JDK1.8种可以看到,PS GC配合Par Old一起专注于追求吞吐量的场合。

关于以上垃圾收集器的原理请参看我的文章——JVM中的垃圾收集器

2.2 -XX:+PrintGCDateStamps

就是加上了日期相关的内容

2.3 -XX:+PrintGCTimeStamps

加了时间戳,配合-XX:+PrintGCDateStamps的效果如下:

2.4 -Xloggc

可以显示指定输出GC日志到文件

3. jps(必会)

jps配合LINUX以下的一些命令可以很方便的完成以下功能
找到CPU、内存消耗最高的JAVA线程
首先jps查看有哪些java进程

主要参数如下:

然后使用Top -Hp 进程号来查看指定进程下使用CPU最多的线程

4.jstat(建议学会)

主要选项如下:

结果各列的含义:
S0 — Heap上的 Survivor space 0 区已使用空间的百分比
S1 — Heap上的 Survivor space 1 区已使用空间的百分比
E — Heap上的 Eden space 区已使用空间的百分比
O — Heap上的 Old space 区已使用空间的百分比
P — Perm space 区已使用空间的百分比
YGC — 从应用程序启动到采样时发生 Young GC 的次数
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC — 从应用程序启动到采样时发生 Full GC 的次数
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)

看了下解释,觉得gccause这个选项比较常用,我们来试试,注意最后可以加上毫秒时间,每隔多少毫秒输出一次:

5.jinfo(不是最重要)

建议打开-XX:+PrintFlagsFinal查看参数默认值,程序运行前会显示所有JVM参数

然后可以使用jinfo -flag 参数名 PID 来查看参数的值

6. jmap(必会)

jmap主要选项如下,注意很多选项都仅仅支持LINUX

6.1 -histo

展示的信息为编号,实例数,字节,类名,注意使用live参数可以只看活着的。例如查看是否存在大量的String对象导致内存溢出

6.2 -heap

查看堆的完整详细信息

内容解释例子

Parallel GC with 13 thread(s)   #13个gc线程  

Heap Configuration:#堆内存初始化配置  
   MinHeapFreeRatio = 40  #-XX:MinHeapFreeRatio设置JVM堆最小空闲比率  
   MaxHeapFreeRatio = 70  #-XX:MaxHeapFreeRatio设置JVM堆最大空闲比率  
   MaxHeapSize      = 8436842496 (8046.0MB)#-XX:MaxHeapSize=设置JVM堆的最大大小  
   NewSize          = 5439488 (5.1875MB) #-XX:NewSize=设置JVM堆的‘新生代’的默认大小  
   MaxNewSize       = 17592186044415 MB  #-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小  
   OldSize          = 5439488 (5.1875MB) #-XX:OldSize=设置JVM堆的‘老生代’的大小  
   NewRatio         = 2 #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率  
   SurvivorRatio    = 8 #-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值  
   PermSize         = 21757952 (20.75MB) #-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小  
   MaxPermSize      = 88080384 (84.0MB) #-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小  

Heap Usage:  
PS Young Generation  
Eden Space:#Eden区内存分布  
   capacity = 87883776 (83.8125MB)  
   used     = 31053080 (29.614524841308594MB)  
   free     = 56830696 (54.197975158691406MB)  
   35.33425782706469% used  
From Space:#其中一个Survivor区的内存分布  
   capacity = 13828096 (13.1875MB)  
   used     = 196608 (0.1875MB)  
   free     = 13631488 (13.0MB)  
   1.4218009478672986% used  
To Space:#另一个Survivor区的内存分布  
   capacity = 16384000 (15.625MB)  
   used     = 0 (0.0MB)  
   free     = 16384000 (15.625MB)  
   0.0% used  
PS Old Generation#当前的Old区内存分布  
   capacity = 156172288 (148.9375MB)  
   used     = 27098208 (25.842864990234375MB)  
   free     = 129074080 (123.09463500976562MB)  
   17.35148299805917% used  
PS Perm Generation#当前的 “永生代” 内存分布  
   capacity = 88080384 (84.0MB)  
   used     = 50847592 (48.492042541503906MB)  
   free     = 37232792 (35.507957458496094MB)  
   57.728622073218936% used

6.3 -dump

输出到文件,导出的文件可以给jhat用


dump出来的文件是二进制的

7.jhat(不是最重要)

用于分析dump文件,但是既然都导出文件了,一般都依靠别的可视化工具来分析了。

jhat会分析dump文件还生成一个HTTP服务器给你访问查看,此处不演示了。

8. jstack

查看JAVA线程的调用堆栈信息。这个可以配合前面的JPS、TOP命令找到最消耗CPU的进程然后做一次详细的分析

  1. 查看最消耗内存和CPU的线程

  1. 查看锁的信息 发现没有死锁

3.可以把信息dump到文件,在根据查出来的线程ID来查询

把线程ID换成十六进制,我们这里的31524是7b24


可以看到是一个定时等待的线程。当然现在其实没啥问题,所以也就不继续分析了

9. 可视化工具jvisualvm(必会)

工具太牛逼。。三言两语肯定说不清楚。可以安装很多插件,这个以后用到慢慢学了。

可以远程访问,但是要求远程启动的java程序在启动时添加如下参数

-Dcom.sun.management.jmxremote.port=<port> 
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.authenticate=false

拿本地来测试下(我这里随便启了个JAVA项目)

实验下,发现OK;

参考资料:
http://blog.csdn.net/yohoph/article/details/42041729
http://ifeve.com/useful-jvm-flags-part-8-gc-logging/
http://www.cnblogs.com/o-andy-o/archive/2013/06/11/3132335.html