JMC java mission control是java性能分析利器.从实践出发,学习如何使用.

本文从JMC Tutorial英文版中提炼出来,涵盖了文中所有重要的topic.请点击下载所有源代码.

Install JMC

从java7自带JMC,示例使用java 8环境.

可以执行JAVA_HOME/bin/jmc来启动JMC.

也可以作为eclipse插件安装,从略.

JFR使用

Java Flight Recorder (JFR)顾名思义,就是java运行情况的黑匣子.

先启动Java进程,然后使用JFR记录程序运行情况.需要在启动命令行加参数 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder

解压源代码,在目录下10_Console_LoadAndDeadlock/bin 启动:java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder LoadAndDeadlock

把jmc关联到java进程

点击启动飞行记录,点击保存.

一分钟之后可以看到应用程序的统计采样.

Hot Methods

目标:使用JMC找到进程中的Hot Spot,也就是耗费时间最多的代码段.

使用JMC代开文件02_JFR_HotMethods/hotmethods.jfr.分析这个JFR记录文件.

可以看到这个耗费最多执行时间点的方法调用

public class Initiator {
private Collection collection;
public Initiator() {
collection = new LinkedList();
}
// Hint: This creates a list of unique elements!
public void initiate(int seed) {
collection.clear();
for (int i = 1; i < 10000; i++) {
if ((i % seed) != 0)
collection.add(i);
}
}
protected Collection getCollection() {
return collection;
}
public int countIntersection(Initiator other) {
int count = 0;
for (Integer i : collection) {
if (other.getCollection().contains(i)) {
count++;
}
}
return count;
}
}
这个代码的问题在于countIntersection方法中是对两个LinkedList做遍历,和元素个数相关10000个元素的平方太大了,改成Hashset存储会修复这个问题.
Latencies
Latencies主要用来监控线程间阻塞等待:打开03_JFR_Latencies/latency.jfr,切换到Events->Graph的tab.我们看到粉红色的事件类型特别多.
 提示信息是阻塞在Logger.log(String)这个方法上.public class Logger {
private static Logger myInstance = new Logger();
public static Logger getLogger() {
return myInstance;
}
public synchronized void log(String text) {
LogEvent event = new LogEvent(CustomEventLatencies.LOG_EVENT_TOKEN, text);
event.begin();
// Do logging here
// Write the text to a database or similar...
try {
// Simulate that this takes a little while.
Thread.sleep(200);
} catch (InterruptedException e) {
// Don't care.
}
event.end();
event.commit();
}
}

可以看到是log方法,如果没有竞争资源的可以去掉synchronized关键字即可.

顺便看看fix之后理想的状态是这样的:

Note:在线程的tab争用的tab下面也提示了是哪个类带来的瓶颈.

GC垃圾回收

GC垃圾回收打开allocator.jfr在04_JFR_GC目录下:

可以看到很多的垃圾回收的动作,虽然都是defnew.新生代的垃圾回收,过于频繁也是问题.

切换到allcations看到对象是Integer,stack trace中看到分配的代码栈,是在Allocator.main->Allocator.go->Integer.valueOf方法中.public class Allocator {
// Integer -> MyAlloc
private HashMap map = new HashMap();//FIX 使用int
private static class MyAlloc {
private int id;
private MyAlloc(int id) {
this.id = id;
}
private int getId() {
return id;
}
}
public static void main(String[] args) {
new Allocator().go();
}
private void go() {
alloc(10000);
long yieldCounter = 0;
while(true) {
Collection myAllocSet = map.values();
for (MyAlloc c : myAllocSet ) {
if (!map.containsKey(c.getId())) //拆装箱
System.out.println("Now this is strange!");
if (++yieldCounter % 1000 == 0)
Thread.yield();
}
}
}
private void alloc(int count) {
for (int i = 0; i < count; i++) {
map.put(i, new MyAlloc(i));
}
}
}

自动拆装箱生成了太多的无用Integer对象,Fix方案也很简单,使用原始类型int代替Integer.

Exception

Exception对于jvm来说比较耗京能,如果抛出太多异常也是需要关注的.

打开exceptions.jfr 在 06_JFR_Exceptions

从上图可以看到应用抛出的异常. 小技巧操作集:在上图中把ScaryException上点击右键加入到操作集合,想知道具体异常在什么时间抛出的去 事件->日志 tab.选择只显示操作集.可以看到异常抛出的时间.

Mbean browser

Mbean的用处就不介绍了.在JMC左侧选取进程,然后选取Mbean 服务器.

会出现当前jvm进程的概览.有cpu内存等指标的dashboard,你可以在dashboard中添加自己关注的指标图表.

我们主要来看Mbean的tab.这里可以选取你关心的指标,可以看到老年代的垃圾收集器是PS MarkSweep.

可以查看当前的Thread相关的信息,比如当前的线程个数.

可以在诊断命令tab中执行想要执行的命令,比如获取线程的执行栈.

线程tab,可以检测死锁情况.点击死锁检测checkbox,看到Thread3 和Thread4是死锁状态.

触发器一栏可以设置知己关心的报警指标和条件.比如CPU使用率过高.

JCMD使用

先运行13_Memleak_Leak/bin下面执行:(参考launcers/Leak.launch)java -Djava.net.preferIPv4Stack=true -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -Xmx128m -Xms128m Leak 2 1

运行jcm命令,看到Leak的进程号是87728. 使用jcmd 87728 help命令查看可以使用的操作命令.

使用jcmd 87728 GC.class_histogram命令得到java堆的使用情况的统计数据.

过几分在运行一次得到当前的的堆的统计情况,可以看到Leak$DemoObject狂涨.

也可以使用jcm来启动一次JFR记录.并把记录文件dump出来jcmd 87728 JFR.start

JFR.dump recording=1 filename=/tmp/leak.jfr

jcmd 87728 JFR.stop recording=1

以上三个命令分别是启动,dump,停止JFR.然后可以打开/tmp/leak.jfr文件来查看JFR记录.

其他weblogic插件因为不常用略过了,感兴趣的同学可以查看原文.

Dtrace 是在Solaris上才能使用,本身对Dtrace背景要求比较高,可以使用Dtrace自带的各种探针.JMC默认是没有Dtrace插件的,先安装.在帮助菜单->安装新软件,选择Dtrace相关的插件.