JDK的可视化工具系列 (四) JConsole、VisualVM

JConsole: Java监视与管理控制台

代码清单1:

import java.util.*;

public class JConsoleDemo {
    
    static class OOMObject {
        public byte[] placeholder = new byte[64 * 1024];
    }

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

    public static void main(String[] args) throws Exception {
        fillHeap(1000);
        //System.gc();
        Thread.sleep(10000);
    }
}

内存监控:

编译运行JConsoleDemo类, 运行时设置的虚拟机参数为 -Xms100m -Xmx100m -XX:+UseSerialGC , 在%JAVA_HOME%\bin目录下, 启动jconsole.exe , 将自动搜索出本机运行的所有虚拟机进程, 这里我们选择JConsoleDemo对应的进程2464。

JDK的可视化工具系列 (四) JConsole、VisualVM_第1张图片

启动后主界面如下: 

JDK的可视化工具系列 (四) JConsole、VisualVM_第2张图片

在"内存"页签, 查看堆内存Eden区的运行趋势如下:

JDK的可视化工具系列 (四) JConsole、VisualVM_第3张图片

从图中详细信息可以看出, Eden区的内存大小为27.328KB, 所以折线图中显示每次到27Mb左右时系统就会进行一次GC。当1000次循环结束后, 执行System.gc(), 柱状图中显示Eden区和Survivor区基本被清空, 但老年代的对应柱状图仍保持峰值状态, 这是因为System.gc()是在fillHeap()方法内执行, 所以list对象在System.gc()执行时仍然是存活的( 处于作用域之内、被引用)。如果将System.gc()移动到fillHeap()方法外执行, 如下柱状图所示, 则会回收包括老年代的所有内存。

JDK的可视化工具系列 (四) JConsole、VisualVM_第4张图片

代码清单2:

import java.io.*;

public class JConsoleDemo2 {
    
    static class SynAddRunalbe implements Runnable {
        int a, b;
        public SynAddRunalbe(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) {
        new Thread(new SynAddRunalbe(1, 2)).start();
        new Thread(new SynAddRunalbe(2, 1)).start();
    }
}

线程监控:

编译运行, 在"线程"页签可查看"死锁"描述。这是因为1、2两个数值在Integer类的缓存常量池[-128, 127]范围内, 这样当多次调用Integer.valueOf()方法时, 不会再每次都创建对象, 而是直接返回缓存常量池中的对象。所以上面两个线程的同步代码块中实际上只创建了两个锁对象, 且在某一时刻互相持有对方的锁, 即"死锁"现象。

JDK的可视化工具系列 (四) JConsole、VisualVM_第5张图片

VisualVM: 多合一故障处理工具

概述与插件安装

VisualVM基于NetBeans平台开发, 因此它一开始就具备了插件扩展的特性, 通过插件支持, VisualVM可以做许多事情, 例如:

  • 显示虚拟机进程和进程的配置、环境信息(jps、jinfo)
  • 监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)
  • dump及分析堆转储快照(jmap、jhat)
  • 方法级的程序运行性能分析, 找出被调用最多、运行时间最长的方法
  • 离线程序快照: 收集程序的运行时配置、线程dump、内存dump等信息建立一个快照, 可以将快照发送开发者处进行bug反馈等等

%JAVA_HOME%\bin目录下, 启动jvisualvm.exe进入主界面, 点击"工具"→"插件"→"可用插件"选项, 选择所需的插件安装。

JDK的可视化工具系列 (四) JConsole、VisualVM_第6张图片

安装好插件后, 选择一个正在运行的java程序就可以查看程序监控的主界面了

JDK的可视化工具系列 (四) JConsole、VisualVM_第7张图片

堆转储快照

两种方式生成堆dump文件: 

  • 在"应用程序"窗口中右键单击应用程序节点, 选择"堆 Dump"
  • 在"监视"页签中选择"堆 Dump"

JDK的可视化工具系列 (四) JConsole、VisualVM_第8张图片

分析程序性能

在Profiler页签中, 可以对程序运行期间方法级的CPU和内存进行分析, 这个操作会对程序运行性能有很大影响, 所以一般不再生产环境使用。CPU分析将会统计每个方法的执行次数、执行耗时; 内存分析则会统计每个方法关联的对象数及对象所占空间。

JDK的可视化工具系列 (四) JConsole、VisualVM_第9张图片

BTrace动态日志跟踪

BTrace既可以作为visualVM的插件来使用, 也可以独立运行。它能在不停止目标程序运行的前提下, 通过热部署技术加入原本并不存在的调试代码, 以实现对程序的动态调试。

上面我们已经安装好了BTrace插件, 这里编译运行BTraceTest。

import java.io.*;

public class BTraceTest {

    public int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) throws IOException {
        BTraceTest test = new BTraceTest();
        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(test.add(a, b));
        }
    }
}

在"应用程序"页签中选择对应的java进程, 右键选择"Trace Application", 进入BTrace面板:

JDK的可视化工具系列 (四) JConsole、VisualVM_第10张图片

在BTrace页签内输入调试代码:

/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript {
    @OnMethod(
    clazz="BTraceTest",
    method="add",
    location=@Location(Kind.RETURN)
)

public static void func(@Self 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)));
}
}

点击BTrace面板的"Start"按钮, 对程序进行动态调试:

BTrace不仅可以用来打印调用堆栈、参数、返回值, 还可以进行性能监控、定位连接泄漏和内存泄漏、解决多线程竞争问题, 这里我们只是做一个入门的了解, 具体的拓展应用以后会专门用一篇文章来讲解。

参考资料

《深入理解Java虚拟机》

你可能感兴趣的:(JDK的可视化工具系列 (四) JConsole、VisualVM)