Java命令行工具之 jstat

Posted Danny_姜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java命令行工具之 jstat相关的知识,希望对你有一定的参考价值。

jstat 是JDK中提供的一个命令行工具,主要用来打印JVM 性能数据相关的统计数据。主要包含以下几个方面:

  • 垃圾回收(GC)方面数据

  • 编译(Compilation)相关数据

  • 类加载信息(Class Loader)

jstat 最大的优势就是可以在JVM运行时,实时的抓取这些数据。

 

如何启动 jstat      

jstat 使用很简单,只要在命令行中执行如下命令:

 jstat -gc -t 11256 10000 10

参数解释:

  • -gc :打印相关的统计参数

  • -t:  在每行日志之前加上JVM的启动时间

  • 11256 : 目标Java进程的ID

  • 10000: jstat命令执行间隔时间(milliseconds),10000表示每10s打印一行日志

  • 10: jstat命令的执行次数,(和上面的时间间隔一起,表示jstat会每10s执行1次,总共执行10次).

执行结果:

上面命令行执行后的结果类似于以下格式:

jstat 打印的信息很多且杂,要了解其中各部分代表的意义需要先对JVM中堆内存有一定的了解。在JVM中,堆内存分为年轻代老年代;年轻代因为使用复制回收算法,也被分为Eden区S0区S1区,如下图所示:

了解了堆内存的分布,再看jstat的打印参数就很容易理解了。

参数意义:

  • time :  JVM启动时间(单位为秒)

  • S0C :年轻代中S0区的容量 (字节)

  • S1C :年轻代中S1区的容量 (字节)

  • S0U :年轻代中S0区目前已使用空间 (字节)

  • S1U :年轻代中S1区目前已使用空间 (字节)

  • EC :年轻代中Eden区的容量 (字节)

  • EU :年轻代中Eden区目前已使用空间 (字节)

  • OC :老年代的容量 (字节)

  • OU :老年代目前已使用空间 (字节)

  • YGC :从应用程序启动到采样时年轻代中GC次数

  • YGCT :从应用程序启动到采样时年轻代中GC所用时间(s)

  • FGC :从应用程序启动到采样时老年代(全GC)GC次数

  • FGCT :从应用程序启动到采样时老年代(全GC)GC所用时间(s)

  • GCT:从应用程序启动到采样时GC用的总时间(s)

解析执行结果

通过上面的jstat打印日志,基本可以得出如下结论:

注意:其实除了 -gc 之外,jstat 还有很多其它子命令

实战演练

我一般使用 jstat 做两个用途

  1.  通过查看 jstat 打印的数据,调整JVM配置参数

  2. 查看代码中是否存在内存泄漏

1. 调整JVM配置参数 

如下代码 JstatDemo.java:

public class JstatDemo 
  static volatile List pigs = new ArrayList();
  static volatile int pigsEaten = 0;
  static final int ENOUGH_PIGS = 1000;


  public static void main(String[] args) throws InterruptedException 
    new PigEater().start();
    new PigDigester().start();
  


  static class PigEater extends Thread 
    @Override
    public void run() 
      while (true) 
        pigs.add(new byte[32 * 1024 * 1024]); //32MB
        if (pigsEaten > ENOUGH_PIGS) return;
        sleep(100);
      
    
  


  static class PigDigester extends Thread 
    @Override
    public void run() 
      long start = System.currentTimeMillis();


      while (true) 
        sleep(2000);
        pigsEaten+=pigs.size();
        pigs = new ArrayList();
        if (pigsEaten > ENOUGH_PIGS)  
          System.out.format("Digested %d pigs in %d ms.%n",pigsEaten, System.currentTimeMillis()-start);
          return;
        
      
    
  
  static void sleep(int ms) 
    try 
      Thread.sleep(ms);
     catch (Exception e) 
      e.printStackTrace();
    
  

上述代码分别使用两个线程 PigEater 和 PigEater 来代表线程执行的吞吐量。

接下来通过配置不同JVM配置,分别执行上述代码,并查看代码执行效果。

配置1:

  • 设置堆内存大小为4G (-Xms4g –Xmx4g)

  • 使用CMS回收器回收老年代(-XX:+UseConcMarkSweepGC),使用Parallel回收算法回收年轻代(-XX:+UseParNewGC)

  • 设置年轻代Eden区大小为512M(-Xmn512m) 

配置2:

  • 设置堆内存大小为2G(-Xms2g –Xmx2g)

  • 使用Parallel回收器回收年轻代和老年代垃圾对象 (-XX:+UseParallelGC)

  • 设置年轻代Eden区为1536M(-Xmn1536m)

分别通过如下两个命令执行配置1和配置2代码,在执行过程中同时执行 jstat 命令

执行结果为:

可以看出配置2的执行时间更短,执行效率更高。可是配置1的分配内存是配置2的2倍,按照正常思维应该更快一些,而结果却是相反。这是为什么呢?如果要分析原因,可以借助于 jstat 工具来分析。

配置1执行 jstat 结果如下:

Timestamp S0C      S1C      S0U      S1U     EC      EU        OC         OU        PC      PU      YGC    YGCT   FGC    FGCT    GCT
594.0   174720.0 174720.0 163844.1  0.0   174848.0 131074.1 3670016.0  2621693.5  21248.0 2580.9   1006   63.182  116   0.236   63.419
595.0   174720.0 174720.0 163842.1  0.0   174848.0 65538.0  3670016.0  3047677.9  21248.0 2580.9   1008   63.310  117   0.236   63.546
596.1   174720.0 174720.0 98308.0 163842.1 174848.0 163844.2 3670016.0   491772.9  21248.0 2580.9   1010   63.354  118   0.240   63.595
597.0   174720.0 174720.0  0.0   163840.1 174848.0 131074.1 3670016.0   688380.1  21248.0 2580.9   1011   63.482  118   0.240   63.723

配置2执行 jstat 结果如下:

Timestamp S0C      S1C     S0U    S1U     EC        EU        OC         OU        PC     PU     YGC     YGCT  FGC   FGCT     GCT
539.3   164352.0 164352.0  0.0    0.0   1211904.0 98306.0   524288.0   164352.2  21504.0 2579.2   27    2.969  141   8.441   11.409
540.3   164352.0 164352.0  0.0    0.0   1211904.0 425986.2  524288.0   164352.2  21504.0 2579.2   27    2.969  141   8.441   11.409
541.4   164352.0 164352.0  0.0    0.0   1211904.0 720900.4  524288.0   164352.2  21504.0 2579.2   27    2.969  141   8.441   11.409
542.3   164352.0 164352.0  0.0    0.0   1211904.0 1015812.6 524288.0   164352.2  21504.0 2579.2   27    2.969  141   8.441   11.409

仔细看可以看出配置1经历了1129次GC事件(YGC + FGC),用时63.723秒(YGCT + FGCT)。而配置2只是经历了168次GC回收,用时11.409秒。大量的GC会造成程序性能降低。

结论:
虽然配置2比配置1的内存小1倍,但是吞吐量却比配置1更高。因此JVM参数的配置还是要根据实际项目、实际情况。

2. 查看代码是否存在内存泄漏

在长时间运行的Java程序中,可以通过运行 jstat 命令连续获取多行GC相关数据,并取这几行数据中的OU(也就是老年代已用容量)的值。

然后,每隔一段较长时间重复一次上述操作,来获取多组OU值。如果这些值呈上升趋势,则说明该Java程序的老年代内存已使用量不断上涨,因此无法被回收的对象在不断增长,很有可能存在内存泄漏。

jstat 缺陷         

很明显 jstat 也不是万能的,最大缺陷就是GC日志不详细。

主要有以下几个方面的信息 jstat 无法获取:

  1. 当多个GC事件发生时,无法获取某单个GC的pause时间

  2. 无法获取sys和user的执行时间

  3. 每次GC事件后,有多少内存被回收掉

因此实际分析问题时,还需要结合其它工具一起分析JVM的性能,其它工具后续会持续介绍。

最后说一句

原创不易,如果觉得写得不错就点个"在看"吧,或者转发更佳????????????

也可以加我微信,探讨更多技术问题

或者关注公众号,查看更多文章

以上是关于Java命令行工具之 jstat的主要内容,如果未能解决你的问题,请参考以下文章

如何通过jstat命令进行查看堆内存使用情况?

JVMjstat命令详解---JVM的统计监测工具

jstat命令详解

jstat命令详解

Java命令学习系列之jstat

jstat 简介