JVM源码分析之Jstat工具原理完全解读

简介:

概述

jstat是hotspot自带的工具,和java一样也位于JAVA_HOME/bin下面,我们通过该工具可以实时了解当前进程的gc,compiler,class,memory等相关的情况,具体我们可以通过jstat -options来看我们到底支持哪些类型的数据,譬如JDK8下的结果是:

-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation

jstat的输出

jstat大家用得其实挺多的,最常见的用法是jstat -gcutil,输出如下:


~ ᐅ jstat -gcutil 692 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  41.49  59.79  83.66  89.92  78.74    295    5.436    10    3.855    9.291
  0.00  41.49  59.80  83.66  89.92  78.74    295    5.436    10    3.855    9.291
  0.00  41.49  59.80  83.66  89.92  78.74    295    5.436    10    3.855    9.291
  0.00  41.49  59.80  83.66  89.92  78.74    295    5.436    10    3.855    9.291
  0.00  41.49  59.80  83.66  89.92  78.74    295    5.436    10    3.855    9.291

那每一列是怎么定义,怎么计算的呢,其实在tools.jar里存在一个文件叫做jstat_options,这个文件里定义了上面的每种类型的输出结果,比如说gcutil

option gcutil {
  column {
    header "^S0^"   /* Survivor 0 Space - Percent Used */
    data (1-((sun.gc.generation.0.space.1.capacity - sun.gc.generation.0.space.1.used)/sun.gc.generation.0.space.1.capacity)) * 100
    scale raw
    align right
    width 6
    format "0.00"
  }

  column {
    header "^S1^"   /* Survivor 1 Space - Percent Used */
    data (1-((sun.gc.generation.0.space.2.capacity - sun.gc.generation.0.space.2.used)/sun.gc.generation.0.space.2.capacity)) * 100
    scale raw
    align right
    width 6
    format "0.00"
  }

  column {
    header "^E^"    /* Eden Space - Percent Used */
    data (1-((sun.gc.generation.0.space.0.capacity - sun.gc.generation.0.space.0.used)/sun.gc.generation.0.space.0.capacity)) * 100
    align right
    scale raw
    width 6
    format "0.00"
  }

  column {
    header "^O^"    /* Old Space - Percent Used */
    data (1-((sun.gc.generation.1.space.0.capacity - sun.gc.generation.1.space.0.used)/sun.gc.generation.1.space.0.capacity)) * 100
    align right
    scale raw
    width 6
    format "0.00"
  }

  column {
    header "^M^"    /* Metaspace Space - Percent Used */
    data (1-((sun.gc.metaspace.capacity - sun.gc.metaspace.used)/sun.gc.metaspace.capacity)) * 100
    align right
    width 6
    scale raw
    format "0.00"
  }

  column {
    header "^CCS^"  /* Compressed Class Space Space - Percent Used */
    data (1-((sun.gc.compressedclassspace.capacity - sun.gc.compressedclassspace.used)/sun.gc.compressedclassspace.capacity)) * 100
    align right
    width 6
    scale raw
    format "0.00"
  }

  column {
    header "^YGC^"  /* Young Generation Collections */
    data sun.gc.collector.0.invocations
    align right
    width 6
    format "0"
  }

  column {
    header "^YGCT^" /* Young Generation Collection Time */
    data sun.gc.collector.0.time/sun.os.hrt.frequency
    align right
    scale sec
    width 8
    format "0.000"
  }

  column {
    header "^FGC^"  /* Full Collections */
    data sun.gc.collector.1.invocations
    align right
    width 5
    scale raw
    format "0"
  }

  column {
    header "^FGCT^" /* Full Collection Time */
    data sun.gc.collector.1.time/sun.os.hrt.frequency
    align right
    scale sec
    width 8
    format "0.000"
  }

  column {
    header "^GCT^"  /* Total Garbage Collection Time */
    data (sun.gc.collector.0.time + sun.gc.collector.1.time)/sun.os.hrt.frequency
    align right
    width 8
    scale sec
    format "0.000"
  }
}

从上面的定义我们知道gcutil的每一列是什么意思,怎么计算出来的,其中类似sun.gc.generation.0.space.0.capacity这样的一些变量是jvm里创建并实时更新的值

jstat如何获取到这些变量的值

变量值显然是从目标进程里获取来的,但是是怎样来的?local socket还是memory share?其实是从一个共享文件里来的,这个文件叫PerfData,主要指的是/tmp/hsperfdata_<user>/<pid>这个文件

PerfData文件

文件创建

这个文件是否存在取决于两个参数,一个UsePerfData,另一个是PerfDisableSharedMem,如果设置了-XX:+PerfDisableSharedMem或者-XX:-UsePerfData,那这个文件是不会存在的,默认情况下PerfDisableSharedMem是关闭的,UsePerfData是打开的,所以默认情况下PerfData文件是存在的。对于UsePerfData和PerfDisableSharedMem这两个参数,这里着重讲一下:

  • UsePerfData:如果关闭了UsePerfData这个参数,那么jvm启动过程中perf memory都不会被创建,默认情况是是打开的
  • PerfDisableSharedMem:该参数决定了存储PerfData的内存是不是可以被共享,也就是说不管这个参数设置没设置,jvm在启动的时候都会分配一块内存来存PerfData,只是说这个PerfData是不是其他进程可见的问题,如果设置了这个参数,说明不能被共享,此时其他进程将访问不了该内存,这样一来,譬如我们jps,jstat等都无法工作。默认这个参数是关闭的,也就是默认支持共享的方式

具体代码在PerfMemory::create_memory_region里

  if (PerfDisableSharedMem) {
    // do not share the memory for the performance data.
    _start = create_standard_memory(size);
  } else {
    _start = create_shared_memory(size);
    if (_start == NULL) {
      // creation of the shared memory region failed, attempt
      // to create a contiguous, non-shared memory region instead.
      //
      if (PrintMiscellaneous && Verbose) {
        warning("Reverting to non-shared PerfMemory region.\n");
      }
      PerfDisableSharedMem = true;
      _start = create_standard_memory(size);
    }
  }

文件删除

那这个文件什么时候删除?正常情况下当进程退出的时候会自动删除,但是某些极端情况下,比如kill -9,这种信号jvm是不能捕获的,所以导致进程直接退出了,而没有做一些收尾性的工作,这个时候你会发现进程虽然没了,但是这个文件其实还是存在的,那这个文件是不是就一直留着,只能等待人为的删除呢,jvm里考虑到了这种情况,会在当前用户接下来的任何一个java进程(比如说我们执行jps)起来的时候会去做一个判断,看/tmp/hsperfdata_<user>下的进程是不是还存在,如果不存在了就直接删除该文件,判断是否存在的具体操作其实就是发一个kill -0的信号看是否有异常。

文件更新

由于这个文件是通过mmap的方式映射到了内存里,而jstat是直接通过DirectByteBuffer的方式从PerfData里读取的,所以只要内存里的值变了,那我们从jstat看到的值就会发生变化,内存里的值什么时候变,取决于-XX:PerfDataSamplingInterval这个参数,默认是50ms,也就是说50ms更新一次值,基本上可以认为是实时的了。

PerfData其他相关VM参数

  • -XX:PerfDataMemorySize:指定/tmp/hsperfdata_<user>下perfData文件的大小,默认是32KB,如果用户设置了该值,jvm里会自动和os的page size对齐,比如linux下pagesize默认是4KB,那如果你设置了31KB,那自动会分配32KB
  • -XX:+PerfDataSaveToFile:是否在进程退出的时候讲PerfData里的数据保存到一个特定的文件里,文件路径由下面的参数指定,否则就在当前目录下
  • -XX:PerfDataSaveFile:指定保存PerfData文件的路径
  • -XX:PerfDataSamplingInterval:指定perfData的采样间隔,默认是50ms,基本算实时采样了

jstat里的坑

本人暂时想到的两大坑:

  • 一次正常的Background CMS GC之后,发现FGC的值加了2次,后面发现主要原因是CMS有init mark和remark两个会暂停应用的阶段,同时因为是对old做gc,因此算了两次

  • JDK8下metaspace的使用情况不准确,比如说CCSC的值表示的是 Compressed Class Space Capacity,但是发现这个值的计算却不是reserve的值,所以我们可能会发现metaspace其实用了非常少,但是通过jstat看起使用率已经非常大了,因此这种情况最好是通过jmx的方式去取那些值做一个计算

size_t CompressedClassSpaceCounters::capacity() {
  return MetaspaceAux::committed_bytes(Metaspace::ClassType);
}


微信公众号:

39a917faff88034b56089cf3d899f1beff305b33

目录
相关文章
|
19天前
|
Rust 安全 Java
JVM原理与实现——Synchronized关键字
在多线程Java程序中,`Synchronized`关键字用于确保线程安全。本文深入探讨其工作原理,通过分析字节码`monitorenter`和`monitorexit`,解释JVM如何实现同步机制。文章展示了`Synchronized`方法的编译结果,并详细解析了轻量锁和重度锁的实现过程,包括Mark Word的状态变化及CAS操作的应用。最后简要介绍了`ObjectMonitor::enter()`函数在获取重度锁时的作用。
JVM原理与实现——Synchronized关键字
|
2月前
|
Arthas Prometheus 监控
监控堆外使用JVM工具
监控堆外使用JVM工具
49 7
|
3月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
3月前
|
JavaScript 前端开发 Java
jvm的jshell,学生的工具
本文介绍了JVM的jshell工具,它为Java平台添加了REPL(读取-评估-打印循环)功能,使得学习、探索编码和原型代码变得更加便捷,但作者认为其在实际开发中较为鸡肋。
52 1
jvm的jshell,学生的工具
|
3月前
|
Arthas 监控 数据可视化
JVM进阶调优系列(7)JVM调优监控必备命令、工具集合|实用干货
本文介绍了JVM调优监控命令及其应用,包括JDK自带工具如jps、jinfo、jstat、jstack、jmap、jhat等,以及第三方工具如Arthas、GCeasy、MAT、GCViewer等。通过这些工具,可以有效监控和优化JVM性能,解决内存泄漏、线程死锁等问题,提高系统稳定性。文章还提供了详细的命令示例和应用场景,帮助读者更好地理解和使用这些工具。
|
3月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
69 2
|
3月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
64 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
3月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
5月前
|
缓存 监控 算法
吃透 JVM 诊断方法与工具使用
【8月更文挑战第4天】深入了解并掌握JVM诊断需把握几大要点:1) 熟悉JVM内存模型,如堆、栈及方法区;2) 掌握垃圾回收机制与算法;3) 运用工具如`jps`(查看Java进程)、`jstat`(监控运行状态)、`jmap`(生成堆快照)、`jhat`(分析堆快照)、`jstack`(检查线程栈); 4) 利用专业工具如Eclipse Memory Analyzer分析堆转储文件查找内存泄漏; 5) 动态监控与调整JVM参数; 6) 结合日志分析性能瓶颈。通过实战案例加深理解,有效应对JVM性能问题。
|
5月前
|
Arthas Prometheus 监控
使用JDK自带工具调优JVM的常用命令
使用JDK自带工具调优JVM的常用命令