JVM命令行监控工具

1、性能优化概述

  • 性能优化大致分为以下三个步骤:

    • ①性能监控
      • 一种以非强行或者入侵方式收集或查看应用运营性能数据的活动。
      • 监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动。
      • 当应用相关干系人提出性能问题却没有提供足够多的线索时,首先我们需要进行性能监控,随后是性能分析。
    • ②性能分析
      • 一种以侵入方式收集运行性能数据的话动,它会影响应用的吞吐量或响应性。
      • 性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。
      • 性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤。
    • ③性能调优
      • 一种为改善应用响应性或香吐量而更改参数、源代码、属性配置的活动,性能调优是在性能监控、性能分析之后的活动。
  • 相关性能指标有:

    • 响应时间

      • 指提交请求和返回该请求的响应之间使用的时间,一般比较关注平均响应时间常用操作的响应时间列表:

      • 在垃圾回收环节中的暂停时间是指执行垃圾收集时,程序的工作线程被暂停的时间。(-XX:MaxGCPauseMillis)

    • 吞吐量

      • 对单位时间内完成的工作量(请求)的量度。
      • 在GC中:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)吞吐量为1-1/(1+n)。(-XX:GCTimeRatio=n,n为程序的运行时间与内存回收的时间之比)
    • 并发数

      • 同一时刻,对服务器有实际交互的请求数。
    • 内存占用

      • Java堆区所占的内存大小。

2、JVM命令行监控工具

2.1 概述

  • 性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。
  • Java作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。

2.2 -jps:查看正在运行的Java进程

  • jps(Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。

  • 说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。

  • 它的基本使用语法为:jps [options] [hostid]

    • options参数(以下参数可以综合使用)

      • -q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等。

      • -l:输出应用程序主类的全类名或如果进程执行的是jar包,则输出jar完整路径。

      • -m:输出虚拟机进程启动时传递给主类main()的参数。

      • -v:列出虚拟机进程启动时的JVM参数。

    • hostid参数

      • RMI注册表中注册的主机名。
      • 如果想要远程监控主机上的java程序,需要安装jstatd。
      • 对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。
      • 如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。
  • 如果某Java班程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java进程。

2.3 -jstat:查看JVM统计信息

  • jstat(JM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

  • 在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。

  • 查看命令相关参数:jstat-h或jstat-help

  • 它的基本使用语法为:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

    • 选项option可以由以下值构成。

      • 类装载相关的:

        • -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等。

      • 垃圾回收相关的:

        测试代码如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
         /**
        * 虚拟机参数:-Xms60m -Xmx60m -XX:SurvivorRatio=8
        * 即设置堆大小为60M,新生代:老年代=2(默认),Eden:S1:S2=8:1:1。
        * 综上所述新生代占20M,老年代占40M,新生代中Eden占16M,S1占2M,S2占2M。
        */
        public class GCTest {
        public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();

        for (int i = 0; i < 1000; i++) {
        byte[] arr = new byte[1024 * 100];//100KB
        list.add(arr);
        try {
        Thread.sleep(100);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        }
        }
        }
        • -gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。运行程序后测试结果如下:

        • -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。运行程序后测试结果如下:

        • -gcutil:显示内容与-gc基本相同,但输出主要关注己使用空间占总空间的百分比。运行程序后测试结果如下:

        • -gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。运行程序后测试结果如下:

        • -gcnew:显示新生代GC状况。运行程序后测试结果如下:

        • -gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间。运行程序后测试结果如下:

        • -gcold:显示老年代GC状况。运行程序后测试结果如下:

        • -gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间。运行程序后测试结果如下:

        • -gcmetacapacity: 显示metaspace的大小。运行程序后测试结果如下:

      • JIT相关的:

        • -compiler:显示JIT编译器编译过的方法、耗时等信息。

        • -printcompilation:输出已经被]IT编译的方法。

    • 其它参数

      • interval参数:用于指定输出统计数据的周期,单位为毫秒。即:查询间隔。如下面的例子每隔一秒查询一次。

      • count参数:用于指定查询的总次数。如下面例子要求查询10次。

      • -t参数:可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒。

        • 我们可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
      • -h参数:可以在周期性数据输出时,输出多少行数据后输出一个表头信息。如下面例子是每隔3秒打印一次表头。

  • jstat还可以用来判断是否出现内存泄漏:

    • ①在长时间运行的Java程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。
    • ②然后,我们每隔一段较长的时间重复一次上述操作,来获得多组OU最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。

2.4 -jinfo:实时查看和修改JVM配置参数

  • jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。

  • 在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。

  • 它的基本使用语法为:jinfo [options] pid

    • -sysprops:

    • -flags:

    • -flag:

  • jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。但是,并非所有参数都支持动态修改。参数只有被标记为manageable的flag可以被实时修改。其实,这个修改能力是极其有限的。

    • 可以查看被标记为manageable的参数(java -XX:+PrintFlagsFinal -version | grep manageable)。

    • 查看或修改参数:

  • java -XX:+PrintFlagslnitial:查看所有JVM参数启动的初始值。

  • java -XX:+PrintFlagsFinal:查看所有VM参数的最终值。

  • java -XX:+PrintCommandLineFlags:查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值。

2.5 -jmap:导出内存映像文件&内存使用情况

  • jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。

  • 开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置。

  • 它的基本使用语法为:

    1
    2
    3
    jmap [option] <pid>
    jmap [option] <executable <core>
    jmap [option] [server_id@]\<remote server IP or hostname>
  • 其中option包括:

  • 作用:

    • ①导出内存映像文件。

      • Heap Dump又叫做堆存储文件,指一个Java进程在某个时间点的内存快照。

      • 通常在写Heap Dump文件前会触发一次Full GC,所以heap dump文件里保存的都是FullGC后留下的对象信息。

      • 由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件则需要耗费更长的时间来完成。

      • -dump:生成Java堆转储快照(dump文件)。特别的:-dump:live只保存堆中的存活对象。

        • 手动方式:

          1
          2
          jmap -dump:format=b,file=<filename.hprof> <pid> 或
          jmap -dump:live,format=b,file=<filename.hprof> <pid>
        • 自动方式:当程序发生OOM退出系统时,一些瞬时信息都随着程序的终止而消失,而重现OOM问题往往比较困难或者耗时。此时若能在OOM时,自动导出dump文件就显得非常迫切。这里介绍一种比较常用的取得堆快照文件的方法,即使用:

          • -XX:+HeapDumpOnOutOfMemoryError:在程序发生OOM时,导出应用程序的当前堆快照。

          • -XX:HeapDumpPath:可以指定堆快照的保存位置。

          • 例如:

            1
            -Xmx100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof
    • ②显示堆内存相关信息。

      • -heap:输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等。

      • 基本语法:

        1
        2
        -jmap -heap pid
        -jmap -histo pid
    • ③其它作用。

      1
      2
      jmap -permstat pid:查看系统的Classloader信息。
      jmap -finalizernfo:查看堆积在finalizer队列中的对象。
  • 小结:

    • 由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。
    • 如果某个线程长时间无法跑到安全点,jmap将一直等下去。
    • 与前面讲的jstat则不同,垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,而stat只需直接读取即可。

2.6 -jhat:JDK自带堆分析工具

  • jhat(JVM Heap Analysis Tool):Sun JDK提供的hat命令与jmlap命令搭配使用,用于分析jmap生成的heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。

  • 使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/,就可以在浏览器里分析。

  • 说明:jhat命令在JDK9、JDK10中已经被删除,官方建议用VisualVM代替。

  • 它的基本使用语法为(jhat [option] [dumpfile]):

    1
    jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>
    • option参数:

2.7 -jstack:打印JVM中线程快照

  • jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

  • 生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。

  • 在thread dump中,要留意下面几种状态

    • 死锁,Deadlock(重点关注)、
    • 等待资源,Waiting on condition(重点关注)
    • 等待获取监视器,Waiting on monitor entry(重点关注)
    • 阻塞,Blocked(重点关注)
    • 执行中,Runnable
    • 暂停,Suspended
    • 对象等待中,object.wait()或TIMED_WAITING
    • 停止,Parked
  • 基本语法(jstack option pid):

    1
    2
    3
    4
    jstack [-l] <pid>
    jstack -F [-m] [-l] <pid>
    jstack [-m] [-l] <executable> <core>
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
    • option参数:

  • java代码层面的监控:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class MonitorTest {
    public static void main(String[] args) {
    new Thread(new Runnable() {
    @Override
    public void run() {
    Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();//追踪当前进程中的所有的线程
    Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
    for(Map.Entry<Thread, StackTraceElement[]> en : entries){
    Thread t = en.getKey();
    StackTraceElement[] v = en.getValue();
    System.out.println("【Thread name is :" + t.getName() + "】");
    for(StackTraceElement s : v){
    System.out.println("\t" + s.toString());
    }
    }
    }
    }).start();
    }
    }

2.8 -jcmd:多功能命令行

  • 在JDK1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。

  • jcmd拥有jmap的大部分功能,并且在Oracle的官方网站上也推荐使用jcmd命令代jmap命令。

  • 基本语法:

    • ①jcmd -l:列出所有的JVM进程。

    • ②jcmd pid help:针对指定的进程,列出支持的所有命令。

    • ③jcmd pid 具体命令:显示指定进程的指令命令的数据。

2.9 -jstatd:远程主机信息收集

  • 之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd工具。

  • 命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。