JMV调优工具

JDK的tools.jar提供

jps:虚拟机进程状况工具

JVM Process Status Tool
可以列出正在运行的虚拟机进程, 并显示虚拟机执行主类(Main Class, main()函数所在的类)的名称, 以及这些进程的本地虚拟机的唯一ID (LVMlD, Local Virtual Machine Identifier)。 虽然功能比较单一, 但它是使用频率最高的JDK命令行工具, 因为其他的JDK工具大多须要输入它查询到的LVMID来确定要监控的是哪一个虚拟机进程。 对于本地虚拟机进程来说,LVMID与操作系统的进程 ID(PID, Process Identifier)是一致的,使用Windows的任务管理器或Unix的ps命令也可以查询到虚拟机进程的LVMID, 但如果同时启动了多个虚拟机进程, 无法根据进程名称定位时, 那就只能依赖jps命令显示主类的功能才能区分了。

jstat : 虚拟机统计信患监视工具

基本命令

首先第一个命令,就是在你们的生产机器linux上,找出你们的Java进程的PID,这个大家自行百度一下即可,用jps命令就可以看到。
接着就针对我们的Java进程执行:jstat -gc PID。这就可以看到这个Java进程(其实本质就是一个JVM)的内存和GC情况了。
除了上面的jstat -gc命令是最常用的以外,他还有一些命令可以看到更多详细的信息,如下所示: jstat -gccapacity PID:堆内存分析 jstat -gcnew PID:年轻代GC分析,这里的TT和MTT可以看到对象在年轻代存活的年龄和存活的最大年龄 jstat -gcnewcapacity PID:年轻代内存分析 jstat -gcold PID:老年代GC分析 jstat -gcoldcapacity PID:老年代内存分析 jstat -gcmetacapacity PID:元数据区内存分析
 

输出结果

运行这个命令之后会看到如下列,给大家解释一下: S0C:这是From Survivor区的大小 S1C:这是To Survivor区的大小 S0U:这是From Survivor区当前使用的内存大小 S1U:这是To Survivor区当前使用的内存大小 EC:这是Eden区的大小 EU:这是Eden区当前使用的内存大小 OC:这是老年代的大小 OU:这是老年代当前使用的内存大小 MC:这是方法区(永久代、元数据区)的大小 MU:这是方法区(永久代、元数据区)的当前使用的内存大小 YGC:这是系统运行迄今为止的Young GC次数 YGCT:这是Young GC的耗时 FGC:这是系统运行迄今为止的Full GC次数 FGCT:这是Full GC的耗时 GCT:这是所有GC的总耗时

技巧

先明确一下,我们分析线上的JVM进程,最想要知道的信息有哪些?
包括如下:新生代对象增长的速率,Young GC的触发频率,Young GC的耗时,每次Young GC后有多少对象是存活下来的,每次Young GC过后有多少对象进入了老年代,老年代对象增长的速率,Full GC的触发频率,Full GC的耗时。

新生代对象增长的速率

我们平时对jvm第一个要了解的事儿,就是随着系统运行,每秒钟会在年轻代的Eden区分配多少对象。
要分析这东西,你只要在线上linux机器上运行如下命令:jstat -gc PID 1000 10
这行命令,他的意思就是每隔1秒钟更新出来最新的一行jstat统计信息,一共执行10次jstat统计
通过这个命令,你可以非常灵活的对线上机器通过固定频率输出统计信息,观察每隔一段时间的jvm中的Eden区对象占用变化。
举个例子,执行这个命令之后,第一秒先显示出来Eden区使用了200MB内存,第二秒显示出来的那行统计信息里,发信Eden区使用了205MB内存,第三秒显示出来的那行统计信息里,发现Eden区使用了209MB内存,以此类推。
此时你可以轻易的推断出来,这个系统大概每秒钟会新增5MB左右的对象。
而且这里大家可以根据自己系统的情况灵活多变的使用,比如你们系统负很低,不一定每秒都有请求,那么可以把上面的1秒钟调整为1分钟,甚至10分钟,去看你们系统每隔1分钟或者10分钟大概增长多少对象。
还有就是一般系统都有高峰和日常两种状态,比如系统高峰期用的人很多,此时你就应该在系统高峰期去用上述命令
看看高峰期的对象增长速率。然后你再得在非高峰的日常时间段内看看对象的增长速率。
按照上述思路,基本上你可以对线上系统的高峰和日常两个时间段内的对象增长速率有很清晰的了解。
 

Young GC的触发频率和每次耗时

接着下一步我们就想知道大概多久会触发一次Young GC,以及每次Young GC的耗时了。
其实多久触发一次Young GC就很容易推测出来了,因为系统高峰和日常时候的对象增长速率你都知道了,那么非常简单就可以推测出来高峰期多久发生一次Young GC,日常期多久发生一次Young GC。
比如你Eden区有800MB内存,那么发现高峰期每秒新增5MB对象,大概高峰期就是3分钟会触发一次Young GC。日常期每秒新增0.5MB对象,那么日常期大概需要半个小时才会触发一次Young GC。
那么每次Young GC的平均耗时呢?
简单,之前给大家说过,jstat会告诉你迄今为止系统已经发生了多少次Young GC以及这些Young GC的总耗时。
比如系统运行24小时后共发生了260次Young GC,总耗时为20s。那么平均下来每次Young GC大概就耗时几十毫秒的时间。你大概就知道每次Young GC的时候会导致系统停顿几十毫秒。

每次Young GC后有多少对象是存活和进入老年代

接着我们想要知道,每次Young GC后有多少对象会存活下来,以及有多少对象会进入老年代。
其实每次Young GC过后有多少对象会存活下来,这个没法直接看出来,但是有办法可以大致推测出来。
之前我们已经推算出来高峰期的时候多久发生一次Young GC,比如3分钟会有一次Young GC
那么此时我们可以执行下述jstat命令:jstat -gc PID 180000 10。这就相当于是让他每隔三分钟执行一次统计,连续执行10次。
此时大家可以观察一下,每隔三分钟之后发生了一次Young GC,此时Eden、Survivor、老年代的对象变化。
正常来说,Eden区肯定会在几乎放满之后重新变得里面对象很少,比如800MB的空间就使用了几十MB。Survivor区肯定会放入一些存活对象,老年代可能会增长一些对象占用。所以这里的关键,就是观察老年代的对象增长速率。
从一个正常的角度来看,老年代的对象是不太可能不停的快速增长的,因为普通的系统其实没那么多长期存活的对象。
如果你发现比如每次Young GC过后,老年代对象都要增长几十MB,那很有可能就是你一次Young GC过后存活对象太多了。
存活对象太多,可能导致放入Survivor区域之后触发了动态年龄判定规则进入老年代,也可能是Survivor区域放不下了,所以大部分存活对象进入老年代。
最常见的就是这种情况。如果你的老年代每次在Young GC过后就新增几百KB,或者几MB的对象,这个还算情有可缘,但是如果老年代对象快速增长,那一定是不正常的。
所以通过上述观察策略,你就可以知道每次Young GC过后多少对象是存活的,实际上Survivor区域里的和进入老年代的对象,都是存活的。
你也可以知道老年代对象的增长速率,比如每隔3分钟一次Young GC,每次会有50MB对象进入老年代,这就是年代对象的增长速率,每隔3分钟增长50MB。
 

Full GC的触发时机和耗时

只要知道了老年代对象的增长速率,那么Full GC的触发时机就很清晰了,比如老年代总共有800MB的内存,每隔3分钟新增50MB对象,那么大概每小时就会触发一次Full GC。
然后可以看到jstat打印出来的系统运行起劲为止的Full GC次数以及总耗时,比如一共执行了10次Full GC,共耗时30s,每次Full GC大概就是需要耗费3s左右。
 

jmap : Java内存映像工具

jmap (Memory Map for Java) 命令用于生成堆转储快照(一般称为 heapdump 或dump 文件)。 如果不使用jmap 命令, 要想获取 Java 堆转储快照还有一些比较 “ 暴力 ”
譬如在第 2 章中用过的 -XX:+HeapDumpOnOutOtMemory Error 参数, 可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件, 通过 -XX:+HeapDumpOnCtr!Break 参数则可以使用 [Ctrl)+[Break) 键让虚拟机生成 dump 文件, 又或者在 Linux 系统下通过Kill -3 命令发送进程退出信号 “ 恐吓 ” 一下虚拟机, 也能拿到 dump 文件。 jmap 的作用并不仅仅是为了获取 dump 文件, 它还可以查询 finalize 执行队列, Java堆和永久代的详细信息, 如空间使用率、 当前用的是哪种收集器等。

stack : Java堆栈跟踪工具

jstack (Stack Trace for Java)命令用千生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。 线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合, 生成线程快照的主要目的是定位线程出现长时间停顿的原因, 如线程间死锁、 死循环 请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈, 就可以知道没有响应的线程到底在后台做些什么事情, 或者等待着什么资源。

JConsole (Java Monitoring and Management Console)

是一款基于JMX的可视化监视和管理的工具。它管理部分的功能是针对JMX MBean进行管理,由于MBean可以使用代码、中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问
线程监控
static class SynAddRunnable implements Runnable { int a, b; public SynAddRunnable(int a, int b) { this.a = a; this.b = b; } public void run() { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(new SynAddRunnable(1,2)).start(); new Thread(new SynAddRunnable(2,1)).start(); } } }
内存监控
static class OOMObject { public byte[] placeholder = new byte[64 * 1024]; } public static void fillHeap(int num) throws InterruptedException { List<OOMObject> list = new ArrayList<OOMObject>(); for (int i = 0; i < num; i++) { //延时 Thread.sleep(50); System.out.println("num: "+i+1); list.add(new OOMObject()); } } public static void main(String[] args) throws InterruptedException { fillHeap(1000); // System.gc(); }

VisualVM (All in one Java Troubleshooting Tool )

除了运行监视,故障处理外,还提供了很多其他功能,如性能分析