文章目录
  1. 1. 虚拟机监控和处理工具
    1. 1.1. JDK命令行工具
      1. 1.1.1. jps:虚拟机进程状况工具
      2. 1.1.2. jstat:虚拟机统计信息监工具
      3. 1.1.3. jinfo:Java配置信息工具
      4. 1.1.4. jmap:Java内存映像工具
      5. 1.1.5. jhat:虚拟机堆转储快照分析工具
      6. 1.1.6. jstack:Java堆栈跟踪工具
    2. 1.2. JDK可视化工具
      1. 1.2.1. JConsole:Java 监视与管理控制台
      2. 1.2.2. VisualVM:多合一故障处理工具
    3. 1.3. 配置RMI方式远程监控JVM

虚拟机监控和处理工具

虚拟机的处理工具主要包括JDK命令行工具和JDK可视化工具,

JDK命令行工具

Jdk命令行工具都在JAVA_HOM/bin目录下,具体如下:

虚拟机性能监控和处理工具-目录

通过上图可以看出每个工具的大小都不是很大,其实它们只是JAVA_HOME/lib/tools.jar类库的一个包装而已,在tools.jar中都包含这些命令的实现代码,tools.jar不属于Java的标准API,如下图:

虚拟机性能监控和处理工具-tools包

下面介绍一下主要的命令行工具:

主要作用 名称
jps JVM Process Status Tool,用于显示指定系统内所有的HotSpot虚拟机进程
jstat JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机运行数据
jinfo Configuration Info for Java,显示虚拟机配置信息
jmap Memory Map for Java,生成虚拟机的内存转储快照(heapdump 文件)
jhat JVM Heap Dump Brower,用于分析headdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果
jstack Stack Trace for Java,显示虚拟机的线程快照

下面将详细介绍每一个命令:

jps:虚拟机进程状况工具

该命令类似于Linux的ps命令,作用也差不多:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class ,main()方法所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。虽然功能比较单一,但是使用频率最高的JDK命令,因为很多命令的操作都是基于LVMID的,并且会显示主类名很多时候都是通过主类名进行区分的。

jps命令格式:

jps [options][hostid]

jsp执行样例:

虚拟机性能监控和处理工具-jps执行样例

远程执行样例:

虚拟机性能监控和处理工具-jps远程执行样例

jps命令各个选项的含义

选项 作用
-q 只输出LVMID,省略主类的名称
-m 输出虚拟机进程启动时传递给主类main()函数的参数
-l 输出主类的全名,如果进程执行的是jar包,输出jar路径
-v 输出JVM启动时JVM参数

jstat:虚拟机统计信息监工具

jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种状态信息的命令行工具。它可以显示本地或者远程虚拟机的类加载、内存、垃圾收集、JIT编译等运行数据。下面来一一展现(这里使用的是Tomcat的实例测试的,VM参数上面演示JPS的时候已经给出):

命令格式:jstat [option vmid [interval[s|ms] [count]]]

jstat -gc 2176 1000 10 每1秒钟对进程2176进行一次垃圾收集查询,共查询10次

虚拟机性能监控和处理工具-jstat-gc

通过上图可以看出每个区的使用情况和Minor GC、Full GC次数和时间、总GC时间,详细如下

1
2
3
4
S0C(Survivor0区总大小) S1C(Survivor1区总大小) S0U(Survivor0区使用量) S1U(Survivor01区使用量) EC(Eden区总大小) EU(Eden区使用量)
OC(Old区总大小) OU(Old区使用量)
PC(持久区总大小) PU(持久区使用量)
YGC(Minor GC次数) YGCT(Minor GC消耗时间(s)) FGC(Full GC次数) FGCT(Full GC时间(s)) GCT(所有GC时间(s))

jstat -gcutil 2176 1000 10 输出每个区占总空间的百分比,每一秒钟输出一次总共输出10次

虚拟机性能监控和处理工具-jstat-gcutil

1
2
3
4
5
6
7
8
9
S0(Survivor0 使用了0%) S0(Survivor0 使用了8.06%)
E(Eden区使用了59%)
O(Old区使用了73%)
P(永久区使用了99%)
YGC(Minor GC次数)
YGCT(Minor GC时间s)
FGC(Full GC次数)
FGCT(Full GC时间s)
GCT(总GC时间s)

下面介绍jstat命令都有哪些option

选 项 作 用
-class 监视类装载、卸载数量、总空间以及装载所消耗的时间
-gc 监视Java堆状况,包括Eden区、两个Survivor区、老年代、永久代等的容量,GC次数、时间合计
-gccapacity 监视内容与-gc基本相同,但是输出关注Java堆各个区域使用到的最大、最小空间
-gcutil 监视内容与-gc基本相同,但是输出关注Java堆各个区域所占百分比
-gccause -gcutil功能一样,但是会额外输出导致上一次GC产生的原因
-gcnew 监视新生代GC状况
-gcnewcapacity 监视内容与-gcnew基本相同,输出主要关注使用最大、最小空间
-gcold 监视老年代GC状况
-gcoldcapacity 监视内容与-gcold输出主要关注使用最大、最小空间
-gcpermcapacity 输出永久代使用到的最大、最小空间
-compiler 输出JIT编译器编译过的方法、耗时等信息
-printcompilation 输出已经被JIT编译好的方法

jinfo:Java配置信息工具

jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。可以使用-sysprops选项把虚拟机进程的System.getProperties()的内容打印出来。改命令在JDK5时,只能在Linux使用;JDK6后可以在Windows使用。

jinfo命令格式:

jinfo [option] <pid>

pid就不用介绍了,option介绍一下:

虚拟机性能监控和处理工具-jinfo-option

-flag <name>表示输出对应VM flag name的值

-flag [+|-]<name>表示设置或者取消指定VM flag name

-flag <name>=<value>表示设置给定的值到指定VM flag name的值

-flags表示输出所有的VM flags

-sysprops表示输出Java的系统属性

演示案例如下:

jinfo -flags

虚拟机性能监控和处理工具-jinfo-flags

jinfo -flag PrintGCDetails

查看打印GC详细信息当前值

虚拟机性能监控和处理工具-jinfo-flag查

动态取消打印GC详情信息

虚拟机性能监控和处理工具-jinfo-flag取消

动态设置打印GC详细信息

虚拟机性能监控和处理工具-jinfo-flag设置

jmap:Java内存映像工具

jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为headdump或dump文件)。jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息。但是在Windows平台下都是受限的。

jmap命令格式:

虚拟机性能监控和处理工具-jmap-命令格式

-heap输出java堆的信息

-histo输出java堆对象的直方图,如果指定了live参数则只统计存活对象的

-permstat输出永久代的信息

-finalizerinfo 输出等待进行finalization的对象的信息

各个option的详细信息:

选择 作用
-dump 生成Java堆转存快照。格式为:-dump:[live,] format=b file=<filename>
-finalizerinfo 显示等待Finalizer线程执行finalize方法的对象,只在Linux/Solaris平台下有效
-heap 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台有效
-histo 显示堆中对象统计信息,包括类、实例数量、合计容量
-permstat 以ClassLoader为统计口径显示永久代内存状态。只在Linux/Solaris平台下有效
-F 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/Solaris平台有效

演示案例:

-dump

虚拟机性能监控和处理工具-jmap-dump

-finalizerinfo

虚拟机性能监控和处理工具-jmap-finalizerinfo

-heap

虚拟机性能监控和处理工具-jmap-heap

-histo

虚拟机性能监控和处理工具-jmap-histo

-permstat

虚拟机性能监控和处理工具-jmap-permstat

jhat:虚拟机堆转储快照分析工具

JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型HTTP服务器,可以通过浏览器查看分析结果。不过在实际情况中一般不会在生成环境的机器上进行分析,而且该命令分析的结果对于我们后面介绍的VisualVM工具分析出来的太过于简单。具体使用如下:

虚拟机性能监控和处理工具-jhat

访问本机的7000端口就可以查看到分析结果

虚拟机性能监控和处理工具-jhat-结果

分析结果默认是以包为单位显示,分析内存泄漏问题主要会使用到其中的”Heap Histogram”与OQL(类似于SQL)页签的功能,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语句。

jstack:Java堆栈跟踪工具

jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照;线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈集合,生成快照的目的是定位出现长时间停顿的原因,如线程死锁、死循环、请求外部资源导致长时间等待等。

jstack命令格式

jstack [option] vmid

虚拟机性能监控和处理工具-jstack

-F,当正常输出的请求不被响应时,强制输出线程堆栈

-l,除堆栈外,显示关于锁的附加信息

-m,如果调用到本地方法的话,可以显示C/C++的堆栈

使用示例如下:

虚拟机性能监控和处理工具-jstack-l

也可以通过代码的方式获取线程相关的信息:

在JDK1.5中,java.lang.Thread类新增了一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象,使用该对象可以实现通过浏览器查看线程堆栈:

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
<%@ page import="java.util.Map" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>服务器线程信息</title>
</head>
<body>
<pre>
<%
for (Map.Entry<Thread, StackTraceElement[]> stackTrace : Thread.getAllStackTraces().entrySet()) {
Thread thread = stackTrace.getKey();
StackTraceElement[] stack = stackTrace.getValue();
if(thread.equals(Thread.currentThread())){
continue;
}
out.print("\n线程:"+thread.getName()+","+thread.getState()+"\n");
for(StackTraceElement element:stack){
out.print("\t"+element+"\t");
}
}
%>
</pre>
</body>
</html>

JDK可视化工具

JConsole:Java 监视与管理控制台

JConsole是一种基于JMX的可视化监视、管理工具。

  • 启动JConsole

    通过JDK/bin目录下的jconsole.exe启动JConsole后,将自动搜索本机运行的所有虚拟机进程。具体如下图:

    虚拟机性能监控和处理工具-jconsole启动页

  • 内存监控

    “内存”页签相当于可视化的jstat命令,用于监视收集器管理JVM内存的变化趋势,通过如下代码来实验一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MonitoringTest {
    static class OOMObject {
    public byte[] placeHolder = new byte[64 * 1024];
    }
    public static void fillHeap(int num) throws InterruptedException {
    ArrayList<OOMObject> oomObjects = new ArrayList<>();
    for (int i = 0; i < num; i++) {
    Thread.sleep(50);
    oomObjects.add(new OOMObject());
    }
    System.gc();
    }
    public static void main(String[] args) throws Exception {
    int read = System.in.read();
    System.out.println(read);
    fillHeap(1000);
    }
    }

    该程序的启动参数,-Xms100m -Xmx100m -XX:+UseSerialGC。这段代码很简单就是以64kb/50毫秒的速度向Java堆填冲数据,一共填冲1000次;我们通过JConsole进行内存监控并和jstat命令对比

    下图是通过命令jstat -gc 10324 250 500 输出的部分结果:

    虚拟机性能监控和处理工具-jconsole-jstat对比

    通过红色的竖框可以看出,eden区空间的增加是有规律的,每次增加320k。这是因为命令是250毫秒输出一次GC信息,程序正好增加了320k的内存。红色的横框表示发生了一次YGC有部分数据进入S1和老年代。下面来看JConsole通过图形的表现:

    虚拟机性能监控和处理工具-jconsole-eden区

    通过图可以看出Eden区的运行趋势呈现折线状,整个趋势也大致符合jstat命令监控出来的数据

  • 线程监控

    在JConsole工具的“线程”页签可以查看线程的信息,类似jstack命令,监控线程长时间停顿的组要原因:等待外部资源(网络连接)、死循环、锁等待(死锁和活锁)。下面通过代码来:

    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
    public class ThreadMonitoring {
    /**
    * 死循环演示
    */
    public static void createBusyThread() {
    Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
    while (true)
    ;
    }
    }, "testBusyThread");
    thread.start();
    }
    /**
    * 线程锁等待
    * @param lock
    */
    public static void createLockThread(final Object lock) {
    Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
    synchronized (lock){
    try {
    lock.wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    },"testLockThread");
    thread.start();
    }
    public static void main(String[] args) throws IOException {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
    bufferedReader.readLine();
    createBusyThread();
    bufferedReader.readLine();
    Object obj = new Object();
    createLockThread(obj);
    }
    }

    程序运行后进入JConsole界面,点击“线程”选项页,如下:

    虚拟机性能监控和处理工具-jconsole-线程监控-main

    可以看到main线程在等待read流;我们在程序运行控制输入字符回车,这个时候线程testBusyThread开始运行,如下

    虚拟机性能监控和处理工具-jconsole-线程监控-testBusyThread

    虚拟机性能监控和处理工具-jconsole-线程监控-main2

    该线程一值在运行,并且运行的代码在ThreadMonitoring.java的20行,main线程又是阻塞状态。再次在控制台输入字符,继续程序的执行,结果如下:

    虚拟机性能监控和处理工具-jconsole-线程监控-testLockThread

    虚拟机性能监控和处理工具-jconsole-线程监控-testBusyThread2

可以看出main线程已经结束,testLockThread线程是WAITING状态,是在ThreadMonitoring.java的37行,等待lock对象执行notify或notifyAll方法,testBusyThread2线程还是一直处于运行状态。

死锁演示:

代码如下:

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
public class ThreadMonitoring {
static class SynAddRunnable implements Runnable {
int a;
int b;
public SynAddRunnable(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (Integer.valueOf(a)) {
synchronized (Integer.valueOf(b)) {
System.out.println(a + b);
}
}
}
}
public static void main(String[] args) throws IOException {
for(int i=0;i<100;i++){
new Thread(new SynAddRunnable(1,2)).start();
new Thread(new SynAddRunnable(2,1)).start();
}
}
}

代码是开200个线程去分别计算1+2以及2+1的值。这里使用了Integer.valueOf()方法,该方法是基于减少对象创建次数和节省内存的考虑,[-128,127]之间的数字会被缓存,当valueOf()方法传入参数在该范围之内,将直接返回缓存中的对象,也就是循环200次,只公用两个对象;确实也出现了死锁,如下:

虚拟机性能监控和处理工具-jconsole-死锁1

虚拟机性能监控和处理工具-jconsole-死锁2

虚拟机性能监控和处理工具-jconsole-死锁3

通过上图可以看出出现了死锁,而且不同线程在等待其他线程的资源,导致线程卡住,不存在等待锁被释放的希望

VisualVM:多合一故障处理工具

VisualVM是目前为止随JDK发布的功能最强大的运行监控处理程序。官方文档介绍VisualVM的软件说明中写上了“All in One”,

  • 插件安装

    点击菜单,工具->插件,有可用插件和已安装插件,具体如下:

    VisualVM-插件安装

    插件中心网址是:https://visualvm.github.io/

  • 生成、浏览堆转存快照

    在VisualVM中生成dump文件有两种方式:

    • 在”Applications”窗口中右键点击应用程序节点,然后选择“堆dump”
    • 在”Applications”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中点击“堆dump”
  • 分析程序性能

    在Profiler页签中,VisualVM提供了程序运行期间方法级的CPU执行时间分析以及内存分析

  • BTrace动态日志跟踪

    BTrace是一个VisualVM插件,本身也是可以独立运行的程序,这是官网https://github.com/btraceio/btrace。它的作用是在不停止目标程序运行的前提下,通过HotSopt虚拟机的HotSwap技术动态加入原本并不存在的调试代码。具体演示如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class BTraceTest {
    public int add(int a, int b) {
    return a + b;
    }
    public static void main(String[] args) throws IOException {
    BTraceTest bTraceTest = new BTraceTest();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
    for(int i=0;i<10;i++){
    bufferedReader.readLine();
    int a = (int) Math.round(Math.random()*1000);
    int b = (int) Math.round(Math.random()*1000);
    System.out.println(bTraceTest.add(a,b));
    }
    }
    }

    程序运行后,在VisualVM中打开该程序的监视,在BTree页签填充如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /* BTrace Script Template */
    import com.sun.btrace.annotations.*;
    import static com.sun.btrace.BTraceUtils.*;
    @BTrace
    public class TracingScript {
    /* put your code here */
    @OnMethod(
    clazz="com.knight.jvm.BTraceTest",
    method="add",
    location=@Location(Kind.RETURN)
    )
    public static void func(@Self com.knight.jvm.BTraceTest instance,int a,int b,@Return int result){
    println("调用堆栈:");
    jstack();
    println(strcat("方法参数A:",str(a)));
    println(strcat("方法参数b:",str(b)));
    println(strcat("方法结果:",str(result)));
    }
    }

    VisualVM-BTrace插件使用1

VisualVM-BTrace插件使用2

点击“Start”按钮进行编译,出现BTrace up&running 的字样,具体如下图:

VisualVM-BTrace插件使用-编译结果

运行效果图,触发程序执行和BTrace的监控代码:

VisualVM-BTrace插件使用-测试代码运行

VisualVM-BTrace插件使用-监视结果

配置RMI方式远程监控JVM

RMI(远程方法调用),基于该协议可以实现远程监控JVM。jstatd命令是一个RMI的Server应用,用于监控JVM的创建和结束,并且提供接口让监控工具连接到本机的JVM。jstatd命令位于JAVA_HOME/bin 目录下,具体使用方法如下:

在文件vi JAVA_HOME/jre/lib/security/java.policy 末尾的};前添加permission java.security.AllPermission;

启动jstatd,进入JAVA_HOME/bin 目录下 ./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=本机ip &

查看监听端口,默认是1099,还会监听一个随机端口,如下:

虚拟机性能监控和处理工具-jstatd监听端口

关闭防火墙

测试,在远程机器上进行测试,执行最简单的jps命令,如下图访问到了远程的java进程信息

虚拟机性能监控和处理工具-测试RMI

文章目录
  1. 1. 虚拟机监控和处理工具
    1. 1.1. JDK命令行工具
      1. 1.1.1. jps:虚拟机进程状况工具
      2. 1.1.2. jstat:虚拟机统计信息监工具
      3. 1.1.3. jinfo:Java配置信息工具
      4. 1.1.4. jmap:Java内存映像工具
      5. 1.1.5. jhat:虚拟机堆转储快照分析工具
      6. 1.1.6. jstack:Java堆栈跟踪工具
    2. 1.2. JDK可视化工具
      1. 1.2.1. JConsole:Java 监视与管理控制台
      2. 1.2.2. VisualVM:多合一故障处理工具
    3. 1.3. 配置RMI方式远程监控JVM