JDK自带JVM监控工具的简单使用

2016年08月09日

一、命令行工具

1. jstatd

1.1 简介
  • 作用
    • 对外提供远程监控接口,方便jps、jvisualvm等工具进行连接
  • 备注
    • 使用RMI的默认端口1099
1.1 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 创建安全策略文件jstatd.all.policy - 文件名可自定义
grant codebase "file:${java.home}/../lib/tools.jar"{
        permission java.security.AllPermission;
};

# 2. 启动jstatd程序,192.168.146.163为启动jstatd程序的机器IP
➜  ~ jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.146.163

# 3. 查看1099端口端口是否占用
➜  ~ netstat -an | grep 1099
tcp6       0      0 :::1099                 :::*                    LISTEN
➜  ~ lsof -i:1099
COMMAND   PID         USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
jstatd  35250 zhongmingmao   14u  IPv6  92208      0t0  TCP *:rmiregistry (LISTEN)

2. jps

2.1 简介
  • 作用
    • 虚拟机进程状况工具
  • 备注
    • 使用频率最高的JDK命令行工具
    • 可搭配jstatd使用
2.2 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 输出虚拟机启动时传递给主类main函数的参数
➜  ~ jps -m
42627 Jstatd
42710 Jps -mm

# 2. 输出主类的全名,如果进程执行的是Jar包,输出Jar包路径
➜  ~ jps -l
42627 sun.tools.jstatd.Jstatd
42724 sun.tools.jps.Jps

# 3. 输出虚拟机进程启动时JVM参数
➜  ~ jps -v
42738 Jps -Denv.class.path=.:/home/zhongmingmao/Software/jdk1.8.0_101/lib:/home/zhongmingmao/Software/jdk1.8.0_101/jre/lib -Dapplication.home=/home/zhongmingmao/Software/jdk1.8.0_101 -Xms8m
42627 Jstatd -Denv.class.path=.:/home/zhongmingmao/Software/jdk1.8.0_101//lib:/home/zhongmingmao/Software/jdk1.8.0_101/jre/lib -Dapplication.home=/home/zhongmingmao/Software/jdk1.8.0_101 -Xms8m -Djava.security.policy=jstatd.all.policy -Djava.rmi.server.hostname=192.168.146.163

# 4. 输出远程机器(192.168.146.163)的JVM进程信息
➜  ~ jps -l 192.168.146.163
42627 sun.tools.jstatd.Jstatd
42755 sun.tools.jps.Jps

3. jstat

3.1 简介
  • 作用
    • 虚拟机统计信息监视工具
  • 备注
    • 运行期定位虚拟机性能问题的首选工具
    • 可搭配jstatd使用
3.2 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看远程机器(192.168.146.163)上的JVM信息,每5秒执行一次,执行5次
➜  ~ jstat -gcutil 42801@192.168.146.163 5000 5
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
100.00   0.00  74.01  35.37  96.64  88.52     12    0.025     1    0.005    0.030
100.00   0.00  86.20  35.37  96.64  88.52     12    0.025     1    0.005    0.030
100.00   0.00  91.96  35.37  96.64  88.52     12    0.025     1    0.005    0.030
100.00   0.00  97.77  35.37  96.64  88.52     12    0.025     1    0.005    0.030
  0.00  62.37   7.20  39.32  93.66  88.65     13    0.027     1    0.005    0.032

# 字段解释
S0      Survivor 0区的使用比例
S1      Survivor 1区的使用比例
E       Eden区的使用比例
O       老年代的使用比例
YGC     Minor GC的次数
YGCT    Minor GC的耗时
FGC     Full GC的次数
FGCT    Full GC的耗时
GCT     总GC耗时

4. jmap

4.1 简介
  • 作用
    • Java内存映射工具
4.2 示例
1
2
3
➜  ~ jmap -dump:format=b,file=jstatd.bin 42801
Dumping heap to /home/zhongmingmao/jstatd.bin ...
Heap dump file created

5. jhat

5.1 简介
  • 作用
    • 堆转储快照分析工具
  • 备注
    • 与jmap搭配使用
    • 内置HTTP服务器
    • 更优秀的替代工具:MAT(Eclipse Memory Analyzer)、jvisualvm
5.2 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 分析堆转储快照,对外提供HTTP服务
➜  ~ jhat jstatd.bin
Reading from jstatd.bin...
Dump file created Tue Aug 09 20:16:14 CST 2016
Snapshot read, resolving...
Resolving 83184 objects...
Chasing references, expect 16 dots................
Eliminating duplicate references................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

# 对外提供的分析页面
http://192.168.146.163:7000/

6. jstack

6.1 简介
  • 作用
    • Java堆栈跟踪工具
  • 备注
    • 更优秀的替代工具:jvisualvm
6.2 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 仅仅列出部分堆栈信息
➜  ~ jstack -l 42801
2016-08-09 22:42:09
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.101-b13 mixed mode):

"Attach Listener" #39 daemon prio=9 os_prio=0 tid=0x00007fb644001000 nid=0xaa4b waiting on c
ondition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
    - None

"DestroyJavaVM" #18 prio=5 os_prio=0 tid=0x00007fb66800a000 nid=0xa732 waiting on condition
[0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
    - None
....

二、可视化工具

1. jconsole

1.1 简介
  • 相对于命令行工具的优点
    • 集成了命令行工具:jps、jinfo、jstat、jstack等
    • 图形化管理,保留历史数据,而不仅仅是当前时刻的数据
1.2 示例
1.2.1 内存监控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 代码
class OOMObject {
    private byte[] placeHold = new byte[64 * 1024]; // 64KB
}

public class TestJconsoleMemory {
    private static final String BRGIN_STR = "begin";

    private static void fillHeap(int num) throws InterruptedException {
        List<OOMObject> list = new ArrayList<OOMObject>();
        for (int i = 0; i < num; ++i) {
            Thread.sleep(50);
            list.add(new OOMObject());
        }
        System.gc();
    }

    // JVM Args
    // -XX:+UseSerialGC -Xms100m -Xmx100m  -Xmn30m -XX:SurvivorRatio=8
    public static void main(String[] args) throws InterruptedException, IOException {
        System.gc();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String readLine = null;
        while (!BRGIN_STR.equals((readLine = reader.readLine()))) {
            // enter "begin" after jconsole is ready!
        }
        fillHeap(1000);
    }
}

# 运行结果分析,具体见下图
// 1. -Xms100m -Xmx100m ➔ 堆内存大小为100MB
// 2. -Xmn30m ➔ 新生代总大小为30MB
// 3. -XX:SurvivorRatio=8 ➔ Eden区大小为30MB * 0.8 = 24576KB , Survivor大小为 30MB * 0.1 = 3072KB
// 4. 可以看出新对象优先在Eden区分配,当Eden区内存不足时,进行Minor GC,总共进行了3次,分别对应曲线的三个顶峰下降处

图片失效

1.2.2 线程监控 - 活锁等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 代码
public class TestJconsoleLiveLock {
    private static final String BUSY_STR = "busy";
    private static final String LOCK_STR = "lock";

    private static void createBusyThread() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) ;
            }
        }, "busyThread");
        thread.start();
    }

    private static void createLiveLockThread(final Object lock) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        lock.wait(); // wait for lock.notify() or lock.notifyAll()
                        System.out.println("i' am alive");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "lockThread");
        thread.start();
    }

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line = null;
        while (!BUSY_STR.equals((line = reader.readLine()))) {
            // enter "busy" to run busyThread
        }
        createBusyThread();
        while (!LOCK_STR.equals((line = reader.readLine()))) {
            // enter "lock" to run lockThread
        }
        Object object = new Object();
        createLiveLockThread(object);
    }
}

# 运行结果分析,具体见下图
// lockThread处于正常的活锁等待,只需要object对象的notify()或notifyAll()被调用,lockThread就可以继续执行

图片失效

图片失效

1.2.3 线程监控 - 死锁等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 代码
public class TestJconsoleDeadLock implements Runnable {
    int a, b;

    public TestJconsoleDeadLock(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        synchronized (Integer.valueOf(a)) { //  -128 <= a <= 127 cached , can be locked
            try {
                Thread.sleep(500); // other thread can lock Integer.valueOf(b)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Integer.valueOf(b)) {
                System.out.println(Thread.currentThread().getId());
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new TestJconsoleDeadLock(1, 2), "lockCacheOneThread").start();
        new Thread(new TestJconsoleDeadLock(2, 1), "lockCacheTwoThread").start();
    }
}

# 运行结果分析,具体见下图
// 线程标签页最下面有一个"检测死锁"的功能
// lockCacheOneThread当前处于Blocked状态,锁定了4add8e0d对象,正在等待lockCacheTwoThread锁定的552a66ea对象
// lockCacheTwoThread当前处于Blocked状态,锁定了552a66ea对象,正在等待lockCacheOneThread锁定的4add8e0d对象
// 形成了死锁

图片失效

图片失效

2. jvisualvm

2.1 简介
  • 作用
    • 随JDK发布的功能最强大的运行监视和故障处理程序
  • 优点
    • All-in-One
    • 不依赖于agent,对应用程序的实际性能影响很小,可直接应用于生产环境
    • 支持插件扩展,推荐两款插件
      • BTrace Workbench
        • 动态日志跟踪,不停止目标程序的前提下,加入原本不存在的调试代码
      • Visual GC
    • 集成了命令行工具:jps、jinfo、jstat、jstack、jmap、jhat等
2.2 示例
2.2.1 BTrace Workbench
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Java 代码
public class TestBTrace {
    public int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) throws IOException {
        TestBTrace testBTrace = new TestBTrace();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < 10; ++i) {
            reader.readLine();
            int a = (int) Math.round(Math.random() * 1000);
            int b = (int) Math.round(Math.random() * 1000);
            System.out.println(testBTrace.add(a, b));
        }
    }
}

# 调试代码(TracingScript
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript {
    @OnMethod(clazz="org.zhongmingmao.ch4.TestBTrace",method="add",location=@Location(Kind.RETURN))
    public static void func(@Self org.zhongmingmao.ch4.TestBTrace instance,int a,int b,@Return int result){
        println("statck info");
        jstack();
        println(strcat("a = ",str(a)));
        println(strcat("b = ",str(b)));
        println(strcat("a+b = ",str(result)));
    }
}

# Btrace的页面如下,可以动态地向正在运行的JVM程序插入调试代码

图片失效

2.2.2 Visual GC

图片失效

三、参考资料