转载

Java命令行工具之 jstat

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次).

执行结果:

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

Java命令行工具之 jstat

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

Java命令行工具之 jstat

了解了堆内存的分布,再看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打印日志,基本可以得出如下结论:

Java命令行工具之 jstat

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

Java命令行工具之 jstat

实战演练

我一般使用 jstat 做两个用途

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

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

Java命令行工具之 jstat

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

}

}

}

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

接下来通过配置不同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 命令

Java命令行工具之 jstat

执行结果为:

Java命令行工具之 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

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

Java命令行工具之 jstat

原文  http://mp.weixin.qq.com/s?__biz=MzU3Mjc5NjAzMw==&mid=2247484257&idx=1&sn=555158f962cfa2fad3fd9b5ee43d3f5d
正文到此结束
Loading...