嘘~ 正在从服务器偷取页面 . . .

jvm监控调优工具


JVM常用工具

之所以要使用JVM工具当然是为了定位系统问题。常见的可依赖的数据包括但不限于异常堆栈、虚拟机运行日志、垃圾收集日志、线程快照(threadump/javacore文件)、堆转储快照(heapdump/ hprof文件)等。

一、基础故障处理工具

主要分三类:

(a)商业授权工具。

主要是JMC(Java Mission Control)及它要使用到的JFR(Java Flight Recorder)。

(b)正式支持工具.

这一类工具属于被长期支持的工具,不同平台、不同版本的JDK之间,这类工具可能会略有差异,但是不会出现某一个工具突然消失的情况。

(c)实验性工具。

这一类工具在它们的使用说明中被声明为”没有技术支持,并且是实验性质的”(Unsupported and Experimental)产品,日后可能会转正,也可能会在某个JDK版本中无声无息地消失。但事实上它们通常都非常稳定而且功能强大,也能在处理应用程序性能问题、定位故障时发挥很大的作用。

JDK5需要手动开启JMX:”-Dcom.sun.management.jmxremote“开启JMX管理功能。

1、jps

jps(JVMProcess Status Tool)是虚拟机进程状况工具。

可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)。

可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,参数hostid为RMI注册表中注册的主机名。

特点:功能单一,使用频率最高

命令格式:

jps [ options ] [ hostid ]

如:jsp -l

options主要有4个

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

2、jstat

jstat(JVM Statistics Monitoring Tool)是虚拟机统计信息监视工具。

用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程[插图]虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有GUI图形界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工具。

jstat命令格式为:

jstat [ option vmid [ interval[s|ms] [count]] ]

如果是本地虚拟机进程,VMID与LVMID是一致的;如果是远程虚拟机进程,那VMID的格式应当是:

[protocal:][//]lvmid[@hostname[:port]/servername]

参数interval和count代表查询间隔和次数,如果省略这2个参数,说明只查询一次。

假设需要每250毫秒查询一次进程10164垃圾收集状况,一共查询20次,那命令应当是:

jstat -gc 10164 250 20

选项option代表用户希望查询的虚拟机信息,主要分为三类:类加载、垃圾收集、运行期编译状况。

jstat主要选项

jstat执行样例:

jstat -gcutil 2764
S0      S1      E       O        P        YGC    YGCT     FGC    FGCT     GCT
0.00    0.00    6.20    41.42    47.20    16     0.105    3      0.472    0.577

新生代Eden区(E,表示Eden)使用了6.2%的空间,2个Survivor区(S0、S1,表示Survivor0、Survivor1)里面都是空的,老年代(O,表示Old)和永久代(P,表示Permanent)则分别使用了41.42%和47.20%的空间。程序运行以来共发生MinorGC(YGC,表示Young GC)16次,总耗时0.105秒;发生FullGC(FGC,表示Full GC)3次,总耗时(FGCT,表示Full GC Time)为0.472秒;所有GC总耗时(GCT,表示GC Time)为0.577秒。

如果使用时遇到这样单个字母要认识。

3、jinfo

jinfo(Configuration Info for Java)的是Java配置信息工具。

作用是实时查看和调整虚拟机各项参数。

jps -v可以查看虚拟机启动时显式指定的参数列表。

jinfo -flag可以查看虚拟机未被显式指定的参数的系统默认值。若jdk6以上版本也可以使用-XX:+PrintFlagsFinal查看参数默认值。

jinfo -sysprops可以把虚拟机进程的System.getProperties()的内容打印出来。这个命令在JDK 5时期已经随着Linux版的JDK发布,当时只提供了信息查询的功能,JDK 6之后,jinfo在Windows和Linux平台都有提供,并且加入了在运行期修改部分参数值的能力(可以使用-flag[+|-]name或者-flag name=value在运行期修改一部分运行期可写的虚拟机参数值)。

在JDK 6中,jinfo对于Windows平台功能仍然有较大限制,只提供了最基本的-flag选项。

命令格式:

jinfo  [ option ] pid

执行样例:查询CMSInitiatingOccupancyFraction参数值:

jinfo -flag CMSInitiatingOccupancyFraction 1444
-XX:CMSInitiatingOccupancyFraction=85

4、jmap

jmap—Java内存映像工具。

jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件),还可以查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。

如果不使用jmap命令呢?

(a)使用-XX:+HeapDumpOnOutOfMemoryError参,可以让虚拟机在内存溢出异常出现之后自动生成堆转储快照文件。

(b)通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成堆转储快照文件。

(c)在Linux系统下通过Kill -3命令发送进程退出信号”恐吓”一下虚拟机,也能顺利拿到堆转储快照。

jmap有部分功能在Windows平台下是受限的,除了生成堆转储快照的-dump选项和用于查看每个类的实例、空间占用统计的-histo选项在所有操作系统中都可以使用之外,其余选项都只能在Linux/Solaris中使用。

jmap命令格式:

jmap [ option ] vmid

jmap主要选项

使用jmap生成一个正在运行的Eclipse的堆转储快照文件,12109是通过jps命令查询到的LVMID。

jmap -dump:format=b,file=eclipse.bin 12109
Dumping heap to D:\eclipse.bin ...
Heap dump file created

5、jhat

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

JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/Web服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。

分析工作是一个耗时而且极为耗费硬件资源的过程,一般不直接在应用服务器上这么搞。

jhat的分析功能相对来说比较简陋,与VisualVM,以及专业用于分析堆转Eclipse Memory Analyzer、IBM HeapAnalyzer等相比。

使用jhat分析dump文件:

jhat eclipse.bin
Reading from eclipse.bin...
Dump file created Fri Nov 19 22:07:21 CST 2010
Snapshot read, resolving...
Resolving 1225951 objects...
Chasing references, expect 245 dots....
Eliminating duplicate references...
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

分析结果默认以包为单位进行分组显示,分析内存泄漏问题主要会使用到其中的”Heap Histogram”(与jmap -histo功能一样)与OQL页签的功能,前者可以找到内存中总容量最大的对象,OQL是标准的对象查询语言,使用类似SQL的语法对内存中的对象进行查询统计。关于OQL 《JVM的对象查询OQL》

6、jstack

jstack:Java堆栈跟踪工具。

jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。

线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。

生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈,就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。

jstack命令格式:

jstack  [ option ] vmid

jstack工具主要选项:

选项作用
-F当正常输出的请求不被响应时,强制输出线程堆栈
-l除堆栈外,显示关于锁的附加信息
-m如果遇到本地方法的话,可以显示C/C++的堆栈

从JDK 5起,java.lang.Thread类新增了一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象。使用这个方法可以通过简单的几行代码完成jstack的大部分功能,在实际项目中不妨调用这个方法做个管理员页面,可以随时使用浏览器来查看线程堆栈。

查看线程状况的JSP页面:

<%@ page import="java.util.Map"%>
<html>
<head>
<title>服务器线程信息</title>
</head>
<body>

<%
    for (Map.Entry<Thread, StackTraceElement[]> stackTrace : Thread.getAllStack-Traces().entrySet()) {
        Thread thread = (Thread) stackTrace.getKey();
        StackTraceElement[] stack = (StackTraceElement[]) stackTrace.getValue();
        if (thread.equals(Thread.currentThread())) {
            continue;
        }
        out.print("\n线程:" + thread.getName() + "\n");
        for (StackTraceElement element : stack) {
            out.print("\t"+element+"\n");
        }
    }
%>

</body>
</html>

7、其他

基础工具:

用于支持基本的程序创建和运行。

基础工具

安全:

用于程序签名、设置安全测试等。

安全工具

国际化:

用于创建本地语言文件。

名称主要作用
native2ascii本地编码到ASCII编码的转换器(Native-to-ASCII Converter)用于”任意受支持的字符编码”和与之对应的”ASCII编码和Unicode转义”之间的相互转换。

远程方法调用:

用于跨Web或网络的服务交互。

远程方法调用工具

Java IDL与RMI-IIOP:在JDK 11中结束了十余年的CORBA支持,这些工具不再提供。

Java IDL与RMI-IIOP

部署工具:用于程序打包、发布和部署。

Java IDL与RMI-IIOP

Java Web Start:

名称主要作用
javaws启动Java Web Start并设置各种选项的工具。jdk11已移除。

性能监控和故障处理:

用于监控分析Java虚拟机运行信息,排查问题。

性能监控和故障处理

WebService工具:

与CORBA一起在JDK 11中被移除。

WebService工具

REPL和脚本工具:

REPL和脚本工具

二、可视化故障处理工具

JDK中除了附带大量的命令行工具外,还提供了几个功能集成度更高的可视化工具,可以使用这些可视化工具以更加便捷的方式进行进程故障诊断和调试工作。

这类工具主要包括JConsole、JHSDB、VisualVM和JMC四个。

JConsole、JHSDB是jdk正式成员,无须独立下载,使用完全免费。

VisualVM 已不是JDK中的正式成员,但仍是可以免费下载、使用。

Java Mission Control : JMC需要与HotSpot内部的”飞行记录仪”(Java Flight Recorder,JFR)配合才能工作,而在JDK 11以前,JFR的开启必须解锁OracleJDK的商业特性支持(使用JCMD的VM.unlock_commercial_features或启动时加入-XX:+UnlockCommercialFeatures参数),在生产环境中仍然是需要付费使用。

1、JHSDB

JHSDB基于服务性代理的调试工具。

JDK中提供了JCMD和JHSDB两个集成式的多功能工具箱,整合基础处理工具,更强大。

JCMD、JHSDB和基础工具的对比

工具的命令模式基本相似。使用时借助help选项使用。

JHSDB是一款基于服务性代理(Serviceability Agent,SA)实现的进程外调试工具。

原理:

服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的、主要基于Java语言(含少量JNI代码)实现的API集合。服务性代理以HotSpot内部的数据结构为参照物进行设计,把这些C++的数据抽象出Java模型对象,相当于HotSpot的C++代码的一个镜像。通过服务性代理的API,可以在一个独立的Java虚拟机的进程里分析其他HotSpot虚拟机的内部数据,或者从HotSpot虚拟机进程内存中dump出来的转储快照里还原出它的运行状态细节。服务性代理的工作原理跟Linux上的GDB或者Windows上的Windbg是相似的。

(JHSDB本身对压缩指针的支持存在很多缺陷,建议用64位系统的实验时禁用压缩指针)

-Xmx10m -XX:+UseSerialGC 
-XX:-UseCompressedOops

程序执行后通过jsp查询到测试查询进程ID:

jps -l  //查看进程id
jhsdb hsdb --pid 10987 //打开图形化界面

Tools->Heap Parameters 可以看到堆的相关参数。

Heap Parameters窗口中清楚列出了新生代的Eden、S1、S2和老年代的容量(单位为字节)以及它们的虚拟内存地址起止范围。

使用JDK默认的G1的话,得到的信息应该类似如下所示

Heap Parameters:
garbage-first heap [0x00007f32c7800000, 0x00007f32c8200000] region size 1024K

Windows->Console窗口,使用scanoops命令在Java堆的新生代(从Eden起始地址到To Survivor结束地址)范围内查找ObjectHolder的实例。

hsdb>scanoops 0x00007f32c7800000 0x00007f32c7b50000 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c458 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c480 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c490 JHSDB_TestCase$ObjectHolder

一般可以通过观察起始地址,判断实例对象分配在哪个区。

Tools->Inspector功能可以确认一下虚拟内存地址中存放的对象。

Inspector为我们展示了对象头和指向对象元数据的指针,里面包括了Java类型的名字、继承关系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口方法表(itable)等。

Tools->Compute Reverse Ptrs 可以根据堆中对象实例地址找出引用它们的指针。

命令行也可以实现该功能:revptrs 0x0007f32cdf785

(revptrs命令并不支持查找栈上的指针引用)

JDK 7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射Class对象存放在一起,存储于Java堆之中。

Java Thread窗口选中main线程后点击Stack Memory按钮查看该线程的栈内存。

线程信息、栈帧信息、以及JHSDB自动生成的注释信息。

2、 JConsole

JConsole:Java监视与管理控制台。

JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进行信息收集和参数动态调整。

JMX是一种开放性的技术,不仅可以用在虚拟机本身的管理上,还可以运行于虚拟机之上的软件中,典型的如中间件大多也基于JMX来实现管理与监控。虚拟机对JMX MBean的访问也是完全开放的,可以使用代码调用API、支持JMX协议的管理控制台,或者其他符合JMX规范的软件进行访问。

(1)启动JConsole

通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询。图片就不放了。

双击选择其中一个进程便可进入主界面开始监控。JMX支持跨服务器的管理,也可以使用下面的”远程进程”功能来连接远程服务器,对远程虚拟机进行监控。

进入JConsole主界面,可以看到主界面里共包括”概述””内存””线程””类””VM摘要””MBean”六个页签,”概述”页签里显示的是整个虚拟机主要运行数据的概览信息,包括”堆内存使用情况””线程””类””CPU使用情况”四项信息的曲线图。

(2)内存监控

“内存”页签的作用相当于可视化的jstat命令,用于监视被收集器管理的虚拟机内存(被收集器直接管理的Java堆和被间接管理的方法区)的变化趋势。

运行测试可以设置一下虚拟机参数(也可以不设置)

-Xms100m -Xmx100m -XX:+UseSerialGC

/**
 * 内存占位符对象,一个OOMObject大约占64KB
 */
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);
        list.add(new OOMObject());
    }
    System.gc();
}

public static void main(String[] args) throws Exception {
    fillHeap(1000);
}

这段代码的作用是以64KB/50ms的速度向Java堆中填充数据,一共填充1000次,使用JConsole的”内存”页签进行监视,观察曲线和柱状指示图的变化。

程序运行后,在”内存”页签中应该可以看到内存池Eden区的运行趋势呈现折线状。

虚拟机启动参数只限制了Java堆为100MB,但没有明确使用-Xmn参数指定新生代大小,在没有设置-XX:SurvivorRadio参数时,Eden与Survivor空间比例的默认值为8∶1。

一般默认情况下,新生代约占堆内存的的1/3,老年代约占2/3。

可以从页签的信息上得到验证。

System.gc();不保证一定进行回收。

3、线程监控

“线程”页签的功能就相当于可视化的jstack命令。

遇到线程停顿的时候可以使用这个页签的功能进行分析。。

/**
 * 线程死循环演示
 */
public static void createBusyThread() {
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (true)   // 第41行
            ;
    }
}, "testBusyThread");
thread.start();
}

/**
 * 线程锁等待演示
 */
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 Exception {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    br.readLine();
    createBusyThread();
    br.readLine();
    Object obj = new Object();
    createLockThread(obj);
}

程序运行后,首先在”线程”页签中选择main线程。

堆栈追踪显示BufferedReader的readBytes()方法正在等待System.in的键盘输入,这时候线程为Runnable状态,Runnable状态的线程仍会被分配运行时间,但readBytes()方法检查到流没有更新就会立刻归还执行令牌给操作系统,这种等待只消耗很小的处理器资源。

监控testBusyThread线程。testBusyThread线程一直在执行空循环,从堆栈追踪中看到一直在MonitoringTest.java代码的41行停留,41行的代码为while(true)。这时候线程为Runnable状态,而且没有归还线程执行令牌的动作,所以会在空循环耗尽操作系统分配给它的执行时间,直到线程切换为止,这种等待会消耗大量的处理器资源。

未完。。。。

4、VisualVM

VisualVM:多合-故障处理工具。

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。

它除了常规的运行监视、故障处理外,还将提供其他方面的能力,譬如性能分析(Profiling)。VisualVM的性能分析功能比起JProfiler、YourKit等专业且收费的Profiling工具都不遑多让。

它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直接应用在生产环境中。

(1)VisualVM兼容范围与插件安装

VisualVM基于NetBeans平台开发工具,所以一开始它就具备了通过插件扩展功能的能力,有了插件扩展支持,VisualVM可以做到:

​ (a)显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。

​ (b)监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(jstat、jstack)。

​ (c)dump以及分析堆转储快照(jmap、jhat)。

​ (d)方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法。

​ (e)离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照,可以将快照发送开发者处进行Bug反馈。

​ (f)其他插件带来的无限可能性。

VisualVM在JDK 6 Update 7中首次发布,但并不意味着它只能监控运行于JDK 6上的程序,它具备很优秀的向下兼容性,甚至能向下兼容至2003年发布的JDK 1.4.2版本。

VisualVM主要功能兼容性列表

VisualVM的插件安装:

(A)手工进行安装

VisualVM的插件可以手工进行安装,在网站[插图]上下载nbm包后,点击”工具->插件->已下载”菜单,然后在弹出对话框中指定nbm包路径便可完成安装。独立安装的插件存储在VisualVM的根目录,譬如JDK 9之前自带的VisulalVM,插件安装后是放在JDK_HOME/lib/visualvm中的。

(B)自动安装

VisualVM的自动安装功能已可找到大多数所需的插件,在有网络连接的环境下,点击”工具->插件菜单”,在页签的”可用插件”及”已安装”中列举了当前版本VisualVM可以使用的全部插件,选中插件后在右边窗口会显示这个插件的基本信息,如开发者、版本、功能描述等。

VisualVM中”概述””监视””线程””MBeans”的功能与Jconsole相似。

(2)生成、浏览堆转储快照

在VisualVM中生成堆转储快照文件有两种方式,

​ a)在”应用程序”窗口中右键单击应用程序节点,然后选择”堆Dump”。

​ b)在”应用程序”窗口中双击应用程序节点以打开应用程序标签,然后在”监视”标签中单击”堆Dump”。

生成堆转储快照文件之后,应用程序页签会在该堆的应用程序下增加一个以[heap-dump]开头的子节点,并且在主页签中打开该转储快照。

如果需要把堆转储快照保存或发送出去,就应在heapdump节点上右键选择”另存为”菜单,否则当VisualVM关闭时,生成的堆转储快照文件会被当作临时文件自动清理掉。要打开一个由已经存在的堆转储快照文件,通过文件菜单中的”装入”功能,选择文件即可。

堆页签中的”摘要”面板可以看到应用程序dump时的运行时参数、System.getPro-perties()的内容、线程堆栈等信息;

“类”面板则是以类为统计口径统计类的实例数量、容量信息;

“实例”面板不能直接使用,因为VisualVM在此时还无法确定用户想查看哪个类的实例,所以需要通过”类”面板进入,在”类”中选择一个需要查看的类,然后双击即可在”实例”里面看到此类的其中500个实例的具体属性信息;

“OQL控制台”面板则是运行OQL查询语句的,同jhat中介绍的OQL功能一样。

(3)分析程序性能

在Profiler页签中,VisualVM提供了程序运行期间方法级的处理器执行时间分析以及内存分析。做Profiling分析肯定会对程序运行性能有比较大的影响,所以一般不在生产环境使用这项功能,或者改用JMC来完成,JMC的Profiling能力更强,对应用的影响非常轻微。

要开始性能分析,先选择”CPU”和”内存”按钮中的一个,然后切换到应用程序中对程序进行操作,VisualVM会记录这段时间中应用程序执行过的所有方法。如果是进行处理器执行时间分析,将会统计每个方法的执行次数、执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间。等要分析的操作执行结束后,点击”停止”按钮结束监控过程。

在JDK 5之后,在客户端模式下的虚拟机加入并且自动开启了类共享——这是一个在多虚拟机进程共享rt.jar中类数据以提高加载速度和节省内存的优化,而根据相关Bug报告的反映,VisualVM的Profiler功能会因为类共享而导致被监视的应用程序崩溃,所进行Profiling前,最好在被监视程序中使用-Xshare:off参数来关闭类共享优化。

(4)BTrace动态日志跟踪

BTrace是一个很神奇的VisualVM插件,它本身也是一个可运行的独立程序。

BTrace的作用是在不中断目标程序运行的前提下,通过HotSpot虚拟机的Instrument功能[插图]动态加入原本并不存在的调试代码。这项功能对实际生产中的程序很有意义:如当程序出现问题时,排查错误的一些必要信息时(譬如方法参数、返回值等),在开发时并没有打印到日志之中以至于不得不停掉服务时,都可以通过调试增量来加入日志代码以解决问题。

在VisualVM中安装了BTrace插件后,在应用程序面板中右击要调试的程序,会出现”Trace Application…”菜单,点击将进入BTrace面板。

BTrace的功能演示:产生两个1000以内的随机整数,输出这两个数字相加的结果


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));
        }
    }
}

这段程序已经上线运行,而我们现在又有了新的需求,想要知道程序中生成的两个随机数是什么,但程序并没有在执行过程中输出这一点。此时,在VisualVM中打开该程序的监视,在BTrace页签填充TracingScript的内容,输入调试代码,即可在不中断程序运行的情况下做到这一点。

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

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

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

点击Start按钮后稍等片刻,编译完成后,Output面板中会出现”BTracecode successfuly deployed”的字样。当程序运行时将会在Output面板输出调试信息。

BTrace的用途很广泛,打印调用堆栈、参数、返回值只是它最基础的使用形式,使用BTrace进行性能监视、定位连接泄漏、内存泄漏、解决多线程竞争问题等。

BTrace能够实现动态修改程序行为,是因为它是基于Java虚拟机的Instrument开发的。Instrument是Java虚拟机工具接口(Java VirtualMachine Tool Interface,JVMTI)的重要组件,提供了一套代理(Agent)机制,使得第三方工具程序可以以代理的方式访问和修改Java虚拟机内部的数据。

阿里巴巴开源的诊断工具Arthas也通过Instrument实现了与BTrace类似的功能。

4、Java Mission Control

Java Mission Control:可持续在线的监控工具。

Oracle Java SE Advanced & Suite[插图]与普通OracleJava SE在功能上的主要差别是前者包含了一系列的监控、管理工具,譬如用于企业JRE定制管理的AMC(Java Advanced ManagementConsole)控制台、JUT(Java Usage Tracker)跟踪系统,用于持续收集数据的JFR(Java Flight Recorder)飞行记录仪和用于监控Java虚拟机的JMC(Java Mission Control)。这些功能全部都是需要商业授权才能在生产环境中使用,但根据Oracle Binary Code协议,在个人开发环境中,允许免费使用JMC和JFR。

JFR特点:

(1)JFR是一套内建在HotSpot虚拟机里面的监控和基于事件的信息搜集框架,与其他的监控工具(如JProfiling)相比,它具有”可持续在线”(Always-On)的特性。

(2)JFR在生产环境中对吞吐量的影响一般不会高于1%(甚至号称是Zero Performance Overhead)。

(3)JFR监控过程的开始、停止都是完全可动态的,即不需要重启应用。

(4)JFR的监控对应用也是完全透明的,即不需要对应用程序的源码做任何修改,或者基于特定的代理来运行。

JMC:

(1)Eclipse RCP作为基础框架。

(2)可下载独立程序,也可以作为Eclipse插件安装。

(3)JMC与虚拟机之间同样采取JMX协议进行通信,作为JMX控制台显示来自虚拟机MBean提供的数据。

(4)JMC还可以作为JFR的分析工具,展示来自JFR的数据。

JMC主界面

打开界面之后,双击”飞行记录器”,将会出现”启动飞行记录”窗口。

飞行记录报告里包含以下几类信息:

(a)一般信息:关于虚拟机、操作系统和记录的一般信息。

(b)内存:关于内存管理和垃圾收集的信息。

(c)代码:关于方法、异常错误、编译和类加载的信息。

(d)线程:关于应用程序中线程和锁的信息。

(e)I/O:关于文件和套接字输入、输出的信息。

(f)系统:关于正在运行Java虚拟机的系统、进程和环境变量的信息。

(g)事件:关于记录中的事件类型的信息,可以根据线程或堆栈跟踪,按照日志或图形的格式查看。

JFR工作过程:

JFR开启一系列事件的录制动作,当某个事件发生时,这个事件的所有上下文数据将会以循环日志的形式被保存至内存或者指定的某个文件当中,循环日志相当于数据流被保留在一个环形缓存中,所以只有最近发生的事件的数据才是可用的。JMC从虚拟机内存或者文件中读取并展示这些事件数据,并通过这些数据进行性能分析。

JFR提供的数据质量通常也要比其他工具通过代理形式采样获得或者从MBean中取得的数据高得多。以垃圾搜集为例,HotSpot的MBean中一般有各个分代大小、收集次数、时间、占用率等数据(根据收集器不同有所差别),这些都属于”结果”类的信息,而JFR中还可以看到内存中这段时间分配了哪些对象、哪些在TLAB中(或外部)分配、分配速率和压力大小如何、分配归属的线程、收集时对象分代晋升的情况等,这些就是属于”过程”类的信息,对排查问题的价值是难以估量的。

三、HotSpot虚拟机插件及工具

虚拟机的插件和辅助工具存放在HotSpot源码hotspot/src/share/tools目录下:

主要有:

(1)Ideal Graph Visualizer

用于可视化展示C2即时编译器是如何将字节码转化为理想图,然后转化为机器码的。

(2)Client Compiler Visualizer

用于查看C1即时编译器生成高级中间表示(HIR),转换成低级中间表示(LIR)和做物理寄存器分配的过程。

(3)MakeDeps

帮助处理HotSpot的编译依赖的工具。

(4)Project Creator

帮忙生成Visual Studio的.project文件的工具。

(5)LogCompilation

将-XX:+LogCompilation输出的日志整理成更容易阅读的格式的工具。

(6)HSDIS

即时编译器的反汇编插件。HSDIS可以用于JIT生成代码反汇编

HSDIS是一个被官方推荐的HotSpot虚拟机即时编译代码的反汇编插件,它包含在HotSpot虚拟机的源码当中,在OpenJDK的网站[插图]也可以找到单独的源码下载。

HSDIS插件的作用是让HotSpot的-XX:+PrintAssembly指令调用它来把即时编译器动态生成的本地代码还原为汇编代码输出,同时还会自动产生大量非常有价值的注释,这样可以通过输出的汇编代码来从最本质的角度分析问题。

需要自己下载编译好的插件,直接放到JDK_HOME/jre/bin/server目录(JDK 9以下)或JDK_HOME/lib/amd64/server(JDK 9或以上)中即可使用。如果没有找到所采用操作系统的对应编译成品,那就自己用源码编译一遍(网上能找到各种操作系统下的编译教程)。

使用SlowDebug或者FastDebug版的HotSpot,可以直接通过-XX:+PrintAssembly指令使用的插件;

如果使用Product版的HotSpot,则还要额外加入一个-XX:+UnlockDiagnosticVMOptions参数才可以工作。

相关示例涉及汇编语言,参考《深入理解Java虚拟机第三版》第四章4.4节。

JITWatch 是HSDIS经常搭配使用的可视化的编译日志分析工具,为便于在JITWatch中读取,可使用以下参数把日志输出到logfile文件。

-XX:+UnlockDiagnosticVMOptions
-XX:+TraceClassLoading
-XX:+LogCompilation
-XX:LogFile=/tmp/logfile.log
-XX:+PrintAssembly
-XX:+TraceClassLoading

在JITWatch中加载日志后,就可以看到执行期间使用过的各种对象类型和对应调用过的方法了,选择想要查看的类和方法,即可查看对应的Java源代码、字节码和即时编译器生成的汇编代码。



版权声明: 本博客所有文章除特別声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明来源 Small-Rose / 张小菜 !
评论
  目录