JVM 调优工具及辅助工具

命令工具

jps(JVM Process Status Tool)

虚拟机进程状况工具。命令格式:

jps [ options ] [ hostid ]

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

参数说明

# 列出Java程序进程ID和Main函数名称
jps
# 只输出进程ID
jps -q
# 输出传递给Java进程(主函数)的参数 
jps -m
# 输出主函数的完整路径
jps -l
# 显示传递给Java虚拟的参数
jps -v

jstat(JVM Statistics Monitoring Tool)

jstat 是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程[1]中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有GUI图形界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工具。在实际生产环境中不一定可以使用图形界面,而且多数服务器管理员也都已经习惯了在文本控制台工作,直接在控制台中使用jstat命令依然是一种常用的监控方式。

jstat命令格式为:

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

参数说明

对于命令格式中的VMID与LVMID需要特别说明一下:如果是本地虚拟机进程,VMID与LVMID 是一致的(为操作系统进程ID);如果是远程虚拟机进程,那VMID的格式应当是:

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

参数interval和count代表查询间隔和次数,如果省略这2个参数,说明只查询一次。假设需要每250 毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:

jstat -gc 2764 250 20

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

选项 作用
-class 监视类加载、卸载数量、总空间以及类装载所耗费的时间
-gc 监视Java堆状况,包括Eden区、2个 Survivor区、老年代、永久代等的容量已用空间,垃圾收集时间合计等信息
-gccapacity 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
-gcutil 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause 与-gcutil功能一样,但是会额外输出导致上一次垃圾收集产生的原因
-gcnew 监视新生代垃圾收集状况
-gcnewcapacity 监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间
-gcold 监视老年代垃圾收集状况
-gcoldcapacity 监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间
-gcpermcapacity 输出永久代使用到的最大、最小空间
-compiler 输出即时编译器编译过的方法、耗时等信息
-printcompilation 输出已经被即时编译的方法
-t 在输出信息前加上一个Timestamp列,显示程序的运行时间
-h 可以在周期性数据输出后,输出多少行数据后,跟着一个表头信息
interval 用于指定输出统计数据的周期,单位为毫秒
count 用于指定一个输出多少次数据

示例

jstat -gc

zhonghongpeng@bogon ~ % jstat -gc 3230 250 4
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
 0.0   4096.0  0.0   4096.0 64512.0  11264.0   455680.0   15242.4   30336.0 29368.5 3456.0 3226.7      3    0.010   0      0.000    0.010
 0.0   4096.0  0.0   4096.0 64512.0  11264.0   455680.0   15242.4   30336.0 29368.5 3456.0 3226.7      3    0.010   0      0.000    0.010
 0.0   4096.0  0.0   4096.0 64512.0  11264.0   455680.0   15242.4   30336.0 29368.5 3456.0 3226.7      3    0.010   0      0.000    0.010
 0.0   4096.0  0.0   4096.0 64512.0  11264.0   455680.0   15242.4   30336.0 29368.5 3456.0 3226.7      3    0.010   0      0.000    0.010
输出列 说明
S0C 年轻代中第一个survivor(幸存区)的容量 (字节)
S1C 年轻代中第二个survivor(幸存区)的容量 (字节)
S0U 年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U 年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC 年轻代中Eden(伊甸园)的容量 (字节)
EU 年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC Old代的容量 (字节)
OU Old代目前已使用空间 (字节)
MC metaspace(元空间)的容量 (字节)
MU metaspace(元空间)目前已使用空间 (字节)
CCSC 压缩类空间大小
CCSU 压缩类空间使用大小
YGC 从应用程序启动到采样时年轻代中gc次数
YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC 从应用程序启动到采样时old代(全gc)gc次数
FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s) GCT:从应用程序启动到采样时gc用的总时间(s)
GCT 从应用程序启动到采样时gc用的总时间(s)

jstat -class

zhonghongpeng@bogon ~ % jstat -class 3230
Loaded  Bytes  Unloaded  Bytes     Time
  4714  9787.2        0     0.0       1.28
输出列 说明
Loaded 已经装载的类的数量
Bytes 装载类所占用的字节数
Unloaded 已经卸载类的数量
Bytes 卸载类的字节数
Time 装载和卸载类所花费的时间

jinfo(Configuration Info for Java)

Java配置信息工具的作用是实时查看和调整虚拟机各项参数。使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了(如果只限于JDK 6或以上版本的话,使用java- XX:+PrintFlagsFinal查看参数默认值也是一个很好的选择)。

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

格式:jinfo [option] <pid>

参数说明

选项 说明
-flag <name> 打印JVM参数<name>的值
-flag [+/-]<name> 在运行期修改部分参数值的
-flag <name>=<value> 在运行期修改部分参数值的
-sysprops 把虚拟机进程的System.getProperties()的内容打印出来
<no option> 打印以上所有
-h/-help 帮助信息

jmap(Memory Map for Java)

jmap命令用于生成堆转储快照(一般称为heapdump或dump文件)。如果不使用jmap命令,要想获取Java堆转储快照也还有一些比较“暴力”的手段:

  • -XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在内存溢出异常出现之后自动生成堆转储快照文件
  • 通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成堆转储快照文件
  • 又或者在Linux系统下通过Kill-3命令发送进程退出信号“恐吓”一下虚拟机,也能顺利拿到堆转储快照。
  • 使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储。
  • 使用 hprof 命令。

jmap的作用并不仅仅是为了获取堆转储快照,它还可以查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。和jinfo命令一样,jmap有部分功能在Windows平台下是受限的,除了生成堆转储快照的-dump选项和用于查看每个类的实例、空间占用统计的-histo选项在所有操作系统中都可以使用之外,其余选项都只能在Linux/Solaris中使用。

命令格式:

  • jmap [option] <pid>
  • jmap [option] <executable> <core>
  • jmap [option] [server_id@]<remote server IP or hostname>

参数说明

参数 作用
pid 需要打印配置信息的进程ID。
executable 产生核心dump的Java可执行文件。
core 需要打印配置信息的核心文件。
server-id 可选的唯一id,如果相同的远程主机上运行了多台调试服务器,用此选项参数标识服务器。(VMID?)
remote server IP or hostname 远程调试服务器的IP地址或主机名。
选项 说明
no option 查看进程的内存映像信息,类似 Solaris pmap 命令。
-heap 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在 Linux/Solaris平台下有效
-dump 生成Java堆转储快照。格式为 -dump:[live,]format=b,file= <filename>, 其中live子参数说明是否只dump出存活的对象
-finalizerinfo 显示在F-Queue中等待 Finalizer线程执行 finalize方法的对象。只在 Linux/ Solaris平下有效
-histo[:live] 显示堆中对象统计信息,包括类、实例数量、合计容量
-F 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/ Solaris平台下有效
-permstat 以 Classloader为统计口径显示永久代内存状态。只在 Linux/ Solaris平台下有效
-clstats 打印类加载器相关统计信息
-J<flag> to pass <flag> directly to the runtime system

jhat(JVM Heap Analysis Tool)

JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。
jhat内置了一个微型的HTTP/Web服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。不过实事求是地说,在实际工作中,除非手上真的没有别的工具可用,否则多数人是不会直接使用jhat命令来分析堆转储快照文件的,主要原因有两个方面:

  1. 一般不会在部署应用程序的服务器上直接分析堆转储快照,即使可以这样做,也会尽量将堆转储快照文件复制到其他机器上进行分析,因为分析工作是一个耗时而且极为耗费硬件资源的过程,既然都要在其他机器上进行,就没有必要再受命令行工具的限制了
  2. 另外一个原因是jhat的分析功能相对来说比较简陋,如VisualVM以及专业用于分析堆转储快照文件的Eclipse Memory Analyzer、IBM HeapAnalyzer等工具,都能实现比jhat更强大专业的分析功能。

命令格式:jhat [ options ] heap-dump-file

参数说明

参数 说明
options 可选命令行参数
heap-dump-file 要查看的二进制Java堆转储文件(Java binary heap dump file)。 如果某个转储文件中包含了多份 heap dumps, 可在文件名之后加上 # 的方式指定解析哪一个 dump, 如: myfile.hprof#3
选项 说明
-stack false/true 关闭对象分配调用栈跟踪(tracking object allocation call stack)。 如果分配位置信息在堆转储中不可用. 则必须将此标志设置为 false. 默认值为 true .
-refs false/true 关闭对象引用跟踪(tracking of references to objects)。 默认值为 true . 默认情况下, 返回的指针是指 向其他特定对象的对象,如反向链接或输入引用(referrers or incoming references), 会统计/计算堆中的 所有对象。
-port port-number 设置 jhat HTTP server 的端口号. 默认值 7000 .
-exclude exclude-file 指定对象查询时需要排除的数据成员列表文件(a file that lists data members that should be excluded from the reachable objects query)。 例如, 如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时, 引用路径涉及 java.lang.String.value 的都会被排除。
-baseline exclude-file 指定一个基准堆转储(baseline heap dump)。 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的(marked as not being new). 其他对象被标记为新的(new). 在比较两个不同的堆转储时很 有用.
-debug int 设置 debug 级别. 0 表示不输出调试信息。 值越大则表示输出更详细的 debug 信息.
-version 启动后只显示版本信息就退出
-help/-h 显示帮助信息并退出
-J<flag> 因为 jhat 命令实际上会启动一个JVM来执行, 通过 -J 可以在启动JVM时传入一些启动参数. 例如, -JXmx512m 则指定运行 jhat 的Java虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个JVM启动参数, 则传入多个 -Jxxxxxx.

示例

执行命令启动服务器

zhonghongpeng@bogon heapdump % jhat ./heapdump-1600348822631.hprof 
Reading from ./heapdump-1600348822631.hprof...
Dump file created Thu Sep 17 21:20:22 CST 2020
Snapshot read, resolving...
Resolving 38612 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

启动如下:

image-20200920093053766

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

jhat 启动后显示的 html ⻚面中包含有:

  • All classes including platform:显示出堆中所包含的所有的类
  • Show all members of the rootset :从根集能引用到的对象
  • Show instance counts for all classes (including platform/excluding platform):显示平台包括的 所有类的实例数量
  • Show heap histogram:堆实例的分布表
  • Show finalizer summary:Finalizer 摘要
  • Execute Object Query Language (OQL) query:执行对象查询语句(OQL)

jstack(Stack Trace for Java)

jstack命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈, 就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。

命令格式:

  • jstack [ option ] pid: 查看当前时间点,指定进程的dump堆栈信息。

  • jstack [ option ] pid > 文件: 将当前时间点的指定进程的dump堆栈信息,写入到指定文件中。

    注:若该文件不存在,则会自动生成;若该文件存在,则会覆盖源文件。

  • jstack [ option ] executable core: 查看当前时间点,core文件的dump堆栈信息。

  • jstack [ option ] [server_id@]<remote server IP or hostname>: 查看当前时间点,远程 机器的dump堆栈信息。

参数说明

选项 说明
-F 当正常输出的请求不被响应时,强制输出线程堆栈
-l 除堆栈外,显示关于锁的附加信息。⻓列表. 打印关于锁的附加信息。例如属于java.util.concurrent的ownable synchronizers 列表,会使得JVM停顿得⻓久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。一般情况不需要使用。
-m 如果调用到本地方法的话,可以显示C/C++的堆栈,一般应用排查不需要使用。

在thread dump中要留意的状态

  • 死锁,Deadlock(重点关注)
  • 等待资源,Waiting on condition(重点关注)
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中,Runnable
  • 暂停,Suspended
  • 对象等待中,Object.wait() 或 TIMED_WAITING
  • 停止,Parked

自己实现

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

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

hprof(Heap/CPU Profiling Tool)

J2SE中提供了一个简单的命令行工具来对java程序的cpu和heap进行 profiling,叫做HPROF。 HPROF实际上是JVM中的一个native的库,它会在JVM启动的时候通过命令行参数来动态加载,并成为 JVM进程的一部分。若要在java进程启动的时候使用HPROF,用户可以通过各种命令行参数类型来使用 HPROF对java进程的heap或者 (和)cpu进行profiling的功能。HPROF产生的profiling数据可以是二 进制的,也可以是文本格式的。这些日志可以用来跟踪和分析 java进程的性能问题和瓶颈,解决内存使用上不优的地方或者程序实现上的不优之处。二进制格式的日志还可以被JVM中的HAT工具来进行浏览 和分析,用 以观察java进程的heap中各种类型和数据的情况。在J2SE 5.0以后的版本中,HPROF已经被 并入到一个叫做Java Virtual Machine Tool Interface(JVM TI)中。

命令格式:

  • java -agentlib:hprof[=options] ToBeProfiledClass
  • java -Xrunprof[:options] ToBeProfiledClass
  • javac -J-agentlib:hprof[=options] ToBeProfiledClass

参数说明

选项 说明 默认
heap=dump|site|sall heap profiling all
cpu=samples|times|old CPU usage off
monitor=y|n monitor contention n
format=a|b text(txt) or binary output a
file=<file> write data to file java.hprof[.txt]
net=<host>:<port> send data over a socket off
depth=<size> stack trace depth 4
interval=<ms> sample interval in ms 10
cutoff=<value> output cutoff point 0.0001
lineno=y|n line number in traces? y
thread=y|n thread in traces? n
doe=y|n dump on exit? y
msa=y|n Solaris micro state accounting n
force=y|n force output to <file> y
verbose=y|n print messages about dumps y

示例

命令示例:

# 每隔20毫秒采样CPU消耗信息,堆栈深度为3,生成的profile文件名称 java.hprof.txt,在当前目录。
java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello

# CPU Usage Times Profiling(cpu=times)的例子,它相对于CPU Usage Sampling Profile能够获得更加细粒度的CPU消耗信息,能够细到每个方法调用的开始和结束,它的实现使用了字节码注入技术 (BCI):
javac -J-agentlib:hprof=cpu=times Hello.java

# Heap Allocation Profiling(heap=sites)的例子:
javac -J-agentlib:hprof=heap=sites Hello.java

# Heap Dump(heap=dump)的例子,它比上面的Heap Allocation Profiling能生成更详细的Heap Dump信息:
javac -J-agentlib:hprof=heap=dump Hello.java

统计方法耗时:

package demo;

/**
 * @author: honphan.john
 * @date: 2020/9/20 10:03
 * @description:
 */
public class HprofTest {

    // java -agentlib:hprof=cpu=times,interval=10 demo.HprofTest demo.HprofTest
    public void slowMethod() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void slowerMethod() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void fastMethod() {
        try {
            Thread.yield();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        HprofTest test = new HprofTest();
        test.fastMethod();
        test.slowMethod();
        test.slowerMethod();
    }
}

虽然在JVM启动参数中加入-Xrunprof:heap=sites参数可以生成CPU/Heap Profile文件,但对JVM性能影响非常大,不建议在线上服务器环境使用。

命令工具参考

基础工具

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

名称 主要作用
appletviewer 在不使用Web浏览器的情况下运行和调试 Applet,JDK11中被移除
extcheck 检査JAR冲突的工具,从JDK9中被移除
jar 创建和管理JAR文件
java Java运行工具,用于运行 Class文件或JAR文件
javac 用于Java编程语言的编译器
javadoc Java的API文档生成器
javah C语言头文件和Stub函数生成器,用于编写JNI方法
javap Java字节码分析工具
jlink 将Module和它的依赖打包成一个运行时镜像文件
jdb 基于JPDA协议的调试器,以类似于GDB的方式进行调试Java代码
jdeps Java类依赖性分析器
ideprscan 用于搜索JAR包中使用了“ deprecated”的类,从JDK9开始提供

安全

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

名称 主要作用
keytool 管理密钥库和证书。主要用于获取或缓存 Kerberos协议的票据授权票据。允许用户査看本地凭据缓存和密钥表中的条目(用于 Kerberos协议)
jarsigner 生成并验证JAR签名
policytool 管理策略文件的GUI工具,用于管理用户策略文件(java. policy),在JDK10中被移除

国际化

用于创建本地语言文件

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

远程方法调用

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

名称 主要作用
rmic Java RMI编译器,为使用JRMP或IIOP协议的远程对象生成Stub、 Skeleton和Tie类,也用于生成 OMG IDL
rmiregistry 远程对象注册表服务,用于在当前主机的指定端口上创建并启动一个远程对象注册表
rmid 启动激活系统守护进程,允许在虚拟机中注册或激活对象
serialver 生成并返回指定类的序列化版本ID

Java IDL与RMI-IOP

在JDK11中结束了十余年的 CORBA支持,这些工具不再提供

名称 主要作用
tnameserv 提供对命名服务的访问IDL转Java编译器(IDL-to- -Java Compiler),生成映射 OMG IDL接口的Java源文件,并启用以Java编程语言编写的使用 CORBA功能的应用程序的Java源文件。IDL意即接口定义语言(Interface Definition Language)
idlj 对象请求代理守护进程( Object Request Broker Daemon),提供从客户端查找和调用 CORBA 环境服务端上的持久化对象的功能。使用ORBD代替瞬态命名服务tnameserv。ORBD包括瞬态orb 命名服务和持久命名服务。ORBD工具集成了服务器管理器、互操作命名服务和引导名称服务器的功能。当客户端想进行服务器时定位、注册和激活功能时,可以与 servertool一起使用
servertool 为应用程序注册、注销、启动和关闭服务器提供易用的接口

部署工具

用于程序打包、发布和部署

名称 主要作用
javapackager 打包、签名Java和 Javafx应用程序,在JDK11中被移除
pack200 使用 Java GZIP压缩器将JAR文件转换为压缩的Pack200文件。压缩的压缩文件是高度压缩的JAR,可以直接部署,节省带宽并减少下载时间
unpack200 将Pack200生成的打包文件解压提取为JAR文件

Java Web Start

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

性能监控和故障处理

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

名称 主要作用
jps JVM Process Status Tool,显示指定系统内所有的 Hotspot虚拟机进程
jstat JVM Statistics Monitoring Tool,用于收集 Hotspot虚拟机各方面的运行数据
jstad JVM Statistics Monitoring Tool Daemon, jstat的守护程序,启动一个RMI服务器应用程序, statd 用于监视测试的 Hotspot虚拟机的创建和终止,并提供一个界面,允许远程监控工具附加到在本地系统上运行的虚拟机。在JDK9中集成到了 JHSDB中
jinfo Configuration Info for Java,显示虚拟机配置信息。在JDK9中集成到了 JHSDB中
jmap Memory Map for Java,生成虚拟机的内存转储快照( headup文件)。在JDK9中集成到了JHSDB中
jhat JVMHeapAnalysisTool,用于分析堆转储快照,它会建立一个HTTP/WEB服务器,让用户可以在浏览器上査看分析结果。在JDK9中被 JHSDB代替
jstack stack Stack Trace for Java,显示虚拟机的线程快照。在JDK9中集成到了 JHSDB中
jhsdb Java Hotspot Debugger,一个基于 Serviceability Agent的 Hotspot进程调试器,从JDK9开始提供
jsadebugd Java Serviceability Agent Debug Daemon,适用于Java的可维护性代理调试守护程序,主要用于附加到指定的Java进程、核心文件,或充当一个调试服务器
jcmd JVM Command,虚拟机诊断命令工具,将诊断命令请求发送到正在运行的Java虚拟机。从JDK7开始提供
jconsole Java Console,用于监控Java虚拟机的使用JMX规范的图形工具。它可以监控本地和远程jconsole Java虚拟机,还可以监控和管理应用程序
jmc Java Mission Control,包含用于监控和管理Java应用程序的工具,而不会引入与这些工具相Jmc 关联的性能开销。开发者可以使用jme命令来创建JMC工具,从JDK7 Update40开始集成到Oraclejdk中
jvisualvm Java Visualvm,一种图形化工具,可在Java虚拟机中运行时提供有关基于Java技术的应用程序(Java应用程序)的详细信息。 Java Visualvm提供内存和CPU分析、堆转储分析、内存泄漏检测、 Mbean访问和垃圾收集。从JDK6 Update7开始提供;从JDK9开始不再打包入JDK 中,但仍保持更新发展,可以独立下载

Web Service工具

与CORBA一起在JDK11中被移除

名称 主要作用
schemagen 用于XML绑定的 Schema生成器,用于生成 XML Schema文件
wsgen XML Web Service2.0的 Java API,生成用于JAX- WS Web Service的JAX-WS便携式产物
wsimport XML Web Service2.0的 Java API,主要用于根据服务端发布的WSDL文件生成客户端
xjc 主要用于根据 XML Schema文件生成对应的Java类

REPL和脚本工具

名称 主要作用
jshell 基于Java的 Shell REPL(Read-Eval-Print Loop)交互工具
jjs 对 Nashorn引擎的调用入口。 Nashorn是基于Java实现的一个轻量级高性能 Javascript运行环境
jrunscript Java命令行脚本外売工具( Command Line Script Shell),主要用于解释执行Javascript、 Groovy、 Ruby等脚本语言

图形化工具

JHSDB

JDK中提供了JCMD和JHSDB两个集成式的多功能工具箱,它们不仅整合了上面的所有基础工具所能提供的专项功能,而且由于有着“后发优势”,能够做得往往比之前的老工具们更好、更强大。

JHSDB虽然名义上是JDK 9中才正式提供,但之前已经以sa-jdi.jar包里面的HSDB(可视化工具)和CLHSDB(命令行工具)的形式存在了很长一段时间。它们两个都是JDK的正式成员,随着JDK一同发布,无须独立下载,使用也是完全免费的。准确来说是Linux和Solaris在OracleJDK 6就可以使用HSDB和CLHSDB了,Windows上要到Oracle-JDK 7才可以用。

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

JCMD、JHSDB与原基础工具实现相同功能的简要对比:

基础工具 JCMD JHSDB
jps -lm jcmd N/A
jmap -dump <pid> jcmd <pid> GC.heap_dump jhsdb jmap --binaryheap
jmap -histo <pid> jcmd <pid> GC.classhistogram jhsdb jmap --histo
jstack <pid> jcmd <pid> Thread.print jhsdb jstack --locks
jinto -sysprops <pid> jcmd <pid> VM.system_properties jhsdb info --sysprops
jinfo -flags <pid> jcmd <pid> VM.flags jhsdb jinfo --flags

实战示例

通过实验来回答一个简单问题:staticObj、instanceObj、localObj这三个变量本身(而不是它们所指向的对象)存放在哪里。

package demo;
/**
 * -Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops
 *
 * @author: honphan.john
 * @date: 2020/9/20 10:55
 * @description:
 */
public class JHSDB_TestCase {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();

        void foo() {
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("done");    // 这里设一个断点
        }
    }
    private static class ObjectHolder {
    }
    public static void main(String[] args) {
        Test test = new JHSDB_TestCase.Test();
        test.foo();
    }
}

首先,我们要确保这三个变量已经在内存中分配好,然后将程序暂停下来,以便有空隙进行实验,这只要把断点设置在代码中加粗的打印语句上,然后在调试模式下运行程序即可。由于JHSDB本身对压缩指针的支持存在很多缺陷,建议用64位系统实验时禁用压缩指针,另外为了后续操作时可以加快在内存中搜索对象的速度,也建议限制一下Java堆的大小:-Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops

程序执行后通过jps查询到测试程序的进程ID,具体如下:

zhonghongpeng@bogon jvm % jps    
6644 Launcher
6645 JHSDB_TestCase
6647 Jps
1119 

启动JHSDB(本次试验基于Java8,在${JAVA_HOME}/bin下还没有JHSDB命令行工具,JDK9及之后使用jhsdb hsdb --pid <pid>)

sudo java -cp ,:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB 6645

image-20200920114157856

下面直接在堆中查找创建的三个对象

首先点击菜单中的Tools->Heap Parameters, 因为运行参数中指定了使用的是Serial收集器,图中我们看到了典型的Serial的分代内存布局,Heap Parameters窗口中清楚列出了新生代的Eden、S1、S2和老年代的容量(单位为字节)以及它们的虚拟内存地址起止范围。

image-20200920114430925

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

image-20200920114738658

果然找出了三个实例的地址,而且它们的地址都落到了Eden的范围之内,算是顺带验证了一般情况下新对象在Eden中创建的分配规则。接下来要根据堆中对象实例地址找出引用它们的指针,原本JHSDB的Tools菜单中有Compute Reverse Ptrs来完成这个功能,但是低版本的貌似都有问题,改为在console面板使用命令行替代(所有图形化操作都可以在console使用命令行替代,输入help获取帮助信息)。

在下图上半部分可以看到使用revptrs命令找到了一个引用了第一个对象"0x000000010f465e70"的对象地址"0x000000010f454270",它存储的是一个Class对象**(一个".class"文件会生成一个JavaClass对象,一些静态成员会称为Class对象的成员,这些信息在JDK7之前在方法区/永久代,JDK7及之后和字符串常量池一起移到了堆/老年代)**。

下半部分使用Tools->Inspector功能根据找到的对象内存地址检查存放的对象。Inspector为我们展示了对象头和指向对象元数据的指针,里面包括了Java类型的名字、继承关系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口方法表(itable)等。

可以看到这个对象有一个成员正是staticObj存储的内容就是"0x000000010f465e70"。

image-20200920115225042

接着我们查找第二个对象被谁引用了。如下图可以看到是一个在内存地址为"0x000000010f465e80"的JHSDB_TestCase$Test对象的一个instanceObj存储了该对象的地址"0x000000010f465e98"。

image-20200920115931689

尝试寻找最后一个对象的时候得到一个空指针,可能该命令还不支持栈上查找:

hsdb> revptrs 0x000000010f465ea8
null
null

在Java Thread窗口选中main线程后点击Stack Memory按钮查看该线程的栈内存。可以直接在栈上找到了对该最后一个对象localObj的引用:

image-20200920120439531

JConsole(Java Monitoring and Management Console)

JConsole是一款基于JMX(Java Management Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进行信息收集和参数动态调整。JMX是一种开放性的技术,不仅可以用在虚拟机本身的管理上,还可以运行于虚拟机之上的软件中,典型的如中间件大多也基于JMX来实现管理与监控。虚拟机对JMX MBean的访问也是完全开放的,可以使用代码调用API、支持JMX协议的管理控制台,或者其他符合JMX规范的软件进行访问。

使用

启动

如果是从命令行启动,使 JDK 在 PATH 上,运行 jconsole 即可。
如果从 GUI shell 启动,找到 JDK 安装路径,打开 bin 文件夹,双击 jconsole 。

连接

  • jconsole启动后会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询。双击选择其中一个进程便可进入主界面开始监控。
  • JMX支持跨服务器的管理,也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行监控
    • 通过jconsole host:port命令
    • 也可以在已经打开的JConsole界面操作:连接->新建连接->选择远程进程->输入远程主机IP和端口号- >点击“连接”

image-20200920155644799

界面介绍

进入视图后包括这六个标签:

Overview:

Displays overview information about the Java VM and monitored values.

Memory: 显示内存使用信息

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

Threads: 显示线程使用信息

“线程”页签的功能就相当于可视化的jstack命令了,遇到线程停顿的时候可以使用这个页签的功能进行分析。如线程长时间停顿的主要原因有等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待等。

示例:
package demo.jconsole;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * @author: honphan.john
 * @date: 2020/9/20 15:55
 * @description:
 */
public class ThreadTest {
    /**
     * 线程死循环演示
     */
    public static void createBusyThread() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true)   // 第19行
                {
                    ;
                }
            }
        }, "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()方法检查到流没有更新就会立刻归还执行令牌给操作系统,这种等待只消耗很小的处理器资源:

image-20200920160644113

死循环线程

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

image-20200920161217055

等待线程

testLockThread线程在等待lock对象的notify()或notifyAll()方法的出现,线程这时候处于WAITING状态,在重新唤醒前不会被分配执行时间。

image-20200920161417066

死锁检测

下面是一段会产生死锁的代码

package demo.jconsole;

/**
 * @author: honphan.john
 * @date: 2020/9/20 16:16
 * @description:
 */
public class DeadLock {
    /**
     * 线程死锁等待演示
     */
    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) {
        for (int i = 0; i < 100; i++) {
            new Thread(new SynAddRunalbe(1, 2)).start();
            new Thread(new SynAddRunalbe(2, 1)).start();
        }
    }
}

使用jconsole点击"检测死锁"即可显示死锁的线程,可以看到现在的线程是"Blocked"状态,和"WAITING"状态,它们是不同的。

image-20200920161811620

Classes

显示类装载信息

image-20200920162217671

VM Summary

显示java VM信息

image-20200920162241281

MBeans: 显示 MBeans

JMX监控指标。

image-20200920162439313

VisualVM(All-in-One Java Troubleshooting Tool)

VisualVM是功能最强大的运行监视和故障处理程序之一, 曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。Oracle曾在VisualVM的软件说明中写上了“All-in-One”的字样,预示着它除了常规的运行监视、故障处理外,还将提供其他方面的能力,譬如性能分析(Profiling)。VisualVM的性能分析功能比起JProfiler、YourKit等专业且收费的Profiling工具都不遑多让。而且相比这些第三方工具,VisualVM还有一个很大的优点:不需要被监视的程序基于特殊Agent去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直接应用在生产环境中。这个优点是JProfiler、YourKit等工具无法与之媲美的。

插件

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

  • 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
  • 监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(jstat、jstack)。
  • dump以及加载分析堆转储快照(jmap、jhat)
  • 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法
  • 离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照,可以将快照发送开发者处进行Bug反馈
  • 查看JFR文件(JMC)
  • 其他插件带来的无限可能性。

VisualVM自带插件安装系统,位置在"Tools->Plugin",但是最近发现它的插件中心好像无法访问了,可以在这个地址进行VisualVM以及它的插件的下载,然后手动安装,插件为".nbm"文件,每个版本不一定携带插件,如果最新版本没有找到插件列表就一直往下一个版本找。

其中一些功能说明

分析程序性能

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

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

image-20200920173128281

BTrace插件动态日志跟踪

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

不过BTrace同时也是一个独立运行的软件,这是官方Github

BTrace能够实现动态修改程序行为,是因为它是基于Java虚拟机的Instrument开发的。Instrument是Java虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)的重要组件,提供了一套代理(Agent)机制,使得第三方工具程序可以以代理的方式访问和修改Java虚拟机内部的数据。阿里巴巴开源的诊断工具Arthas也通过Instrument实现了与BTrace类似的功能

远程连接

配置JMX

tomcat参数

CATALINA_OPTS="-xms800m -xmx800m -xmn350m -XX: Survivorratio=8 -XX: + HeapDumpOnOutOfMemoryError -Dcom.sun.management.jmxremote=true  -Djava.rmi.server.hostname=192.168.1.105 -Dcom.Sun.Management.jmxremote.port=6666 -Dcom.sun.management.jmxremote.ssl=false  -Dcom.Sun.Managementote.ssl=false -Dcom.Sun.management.jmxremote.authenticate=false"

配置jstatd

  1. 自定义一个 statd.policy 文件,添加
grant codebase "jrt:/jdk.jstatd" {
	permission java.security.Allpermission;
};

grant codebase "jrt:/jdk.Internal.jvmstat" {
 	permission java.security.Allpermission;
}
  1. 然后在 JDK_HOME/bin下面运行jstatd,例如:
./jstatd -J-Djava.rmi.hostname=192.168.1.102 -J-Djava.security.policy="上面配置的policy文件路径" -p 1099 &

客户端VisualVM连接

然后在VisualVM分别连接即可

JMC(Java Mission Control)

除了大家熟知的面向通用计算(General Purpose Computing)可免费使用的Java SE外,Oracle公司还开辟过带商业技术支持的Oracle Java SE Support和面向独立软件供应商(ISV)的Oracle Java SE Advanced & Suite产品线。

除去带有7×24小时的技术支持以及可以为企业专门定制安装包这些非技术类的增强服务外, Oracle Java SE Advanced & Suite与普通Oracle Java SE在功能上的主要差别是前者包含了一系列的监控、管理工具,譬如用于企业JRE定制管理的AMC(Java Advanced Management Console)控制台、JUT(Java Usage Tracker)跟踪系统,用于持续收集数据的JFR(Java Flight Recorder)飞行记录仪和用于监控Java虚拟机的JMC(Java Mission Control)。这些功能全部都是需要商业授权才能在生产环境中使用,但根据Oracle Binary Code协议,在个人开发环境中,允许免费使用JMC和JFR,本节笔者将简要介绍它们的原理和使用。

JFR(Java Flight Recorder)

JFR是一套内建在HotSpot虚拟机里面的监控和基于事件的信息搜集框架,与其他的监控工具(如JProfiling)相比,Oracle特别强调它“可持续在线”(Always-On)的特性。JFR在生产环境中对吞吐量的影响一般不会高于1%(甚至号称是Zero Performance Overhead),而且JFR监控过程的开始、停止都是完全可动态的,即不需要重启应用。JFR的监控对应用也是完全透明的,即不需要对应用程序的源码做任何修改,或者基于特定的代理来运行。

JMC最初是BEA公司的产品,因此并没有像VisualVM那样一开始就基于自家的Net-Beans平台来开发,而是选择了由IBM捐赠的Eclipse RCP作为基础框架,现在的JMC不仅可以下载到独立程序,更常见的是作为Eclipse的插件来使用。JMC与虚拟机之间同样采取JMX协议进行通信,JMC一方面作为JMX控制台,显示来自虚拟机MBean提供的数据;另一方面作为JFR的分析工具,展示来自JFR的数据。

启动JMC并连接JVM

  • 本地JVM

    打开JMC后在左侧的“JVM浏览器”面板中自动显示了通过JDP协议(Java Discovery Protocol)找到的本机正在运行的HotSpot虚拟机进程image-20200920180627703

  • 远程JVM

    • 如果需要监控其他远程服务器上的虚拟机,可在“文件->连接”菜单中创建远程连接。然后根据引导输入远程连接信息,通常包括host、port、authenticatin等信息。

    • 另外需要远程服务器的JVM启动时增加参数:

      -Dcom.sun.management.jmxremote.port=9999 
      -Dcom.sun.management.jmxremote.ssl=false 
      -Dcom.sun.management.jmxremote.authenticate=false 
      -Djava.rmi.server.hostname=192.168.31.4 
      -XX:+UnlockCommercialFeatures 
      -XX:+FlightRecorder
      

使用飞行记录仪

双击“飞行记录器”,将会出现“启动飞行记录”窗口。

在启动飞行记录时,可以进行记录时间、垃圾收集器、编译器、方法采样、线程记录、异常记录、网络和文件I/O、事件记录等选项和频率设定。点击“完成”按钮后马上就会开始记录,记录时间结束以后会生成飞行记录报告:

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

  • 一般信息:关于虚拟机、操作系统和记录的一般信息。
  • 内存:关于内存管理和垃圾收集的信息。
  • 代码:关于方法、异常错误、编译和类加载的信息。
  • 线程:关于应用程序中线程和锁的信息。
  • I/O:关于文件和套接字输入、输出的信息。
  • 系统:关于正在运行Java虚拟机的系统、进程和环境变量的信息。
  • 事件:关于记录中的事件类型的信息,可以根据线程或堆栈跟踪,按照日志或图形的格式查看。

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

image-20200920181646036

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

使用命令行启动飞行记录仪

解锁JDK的商业特性:

jcmd 1152 VM.unlock_commercial_features

使用命令行启动JFR

jcmd 41250 JFR.start delay=10s duration=1m filename=/Users/cc/Desktop/log.jfr

保存下来的JFR文件除了JMC可以打开,VisualVm也可以打开。

HotSpot虚拟机插件及工具

一些HotSpot研发过程中编写的插件和工具,这些工具大多数都在JDK源码(存放在HotSpot源码hotspot/src/share/tools目录下)中包含:

  • Ideal Graph Visualizer:用于可视化展示C2即时编译器是如何将字节码转化为理想图,然后转化为机器码的。
  • Client Compiler Visualizer:用于查看C1即时编译器生成高级中间表示(HIR),转换成低级中间表示(LIR)和做物理寄存器分配的过程(源码其实从未进入过HotSpot的代码仓库)。
  • MakeDeps:帮助处理HotSpot的编译依赖的工具。
  • Project Creator:帮忙生成Visual Studio的.project文件的工具。
  • LogCompilation:将-XX:+LogCompilation输出的日志整理成更容易阅读的格式的工具。
  • HSDIS:即时编译器的反汇编插件(OpenJDK源码中的位置:hotspot/src/share/tools/hsdis/)。

HSDIS

HSDIS是一个被官方推荐的HotSpot虚拟机即时编译代码的反汇编插件,它包含在HotSpot虚拟机的源码当中[2],在OpenJDK的网站[3]也可以找到单独的源码下载,但并没有提供编译后的程序。

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

  • 可以根据自己的操作系统和处理器型号,从网上直接搜索(HLLVM圈子有编译好的)、下载编译好的插件,直接放到JDK_HOME/jre/bin/server目录(JDK 9以下)或JDK_HOME/lib/amd64/server(JDK 9或以上)中即可使用。
  • 或者自己用源码编译一遍(网上能找到各种操作系统下的编译教程)。

另外还有一点需要注意,如果读者使用的是SlowDebug或者FastDebug版的HotSpot,那可以直接通过-XX:+PrintAssembly指令使用的插件;如果读者使用的是Product版的HotSpot,则还要额外加入一个-XX:+UnlockDiagnosticVMOptions参数才可以工作。

例子

package john;
/**
 * 参数:
 * -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly //打印编译器的汇编
 * -Xcomp  //使用编译器模式
 * -XX:+TraceClassLoading -XX:+LogCompilation 
 * -XX:LogFile=./logfile.log    //将日志输出到一个文件 
 * -XX:CompileCommand=dontinline,*Bar.sum -XX:CompileCommand=compileonly,*Bar.sum   //不进行方法内联
 * 
 * @author: honphan.john
 * @date: 2020/9/19 08:34
 * @description:
 */
public class Bar {
    int a = 1;
    static int b = 2;

    public int sum(int c) {
        return a + b + c;
    }

    public static void main(String[] args) {
        new Bar().sum(3);
    }
}

在这个例子中测试代码比较简单,肉眼直接看日志中的汇编输出是可行的,但在正式环境中-XX:+PrintAssembly的日志输出量巨大,且难以和代码对应起来,这就必须使用工具来辅助了。

JITWatch

JITWatch是HSDIS经常搭配使用的可视化的编译日志分析工具,为便于在JITWatch中读取。下载JITWatch后即可导入输出的汇编日志,即可进行Java源码、字节码、汇编的对比查看。


  1. 需要远程主机提供RMI支持,JDK中提供了jstatd工具可以很方便地建立远程RMI服务器 ↩︎


   转载规则


《JVM 调优工具及辅助工具》 阿钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录