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

概述

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

1-class-compiler 2-gc 3-gccapacity 4-gccause 5-gcmetacapacity 6-gcnew 7-gcnewcapacity 8-gcold 9-gcoldcapacity 10-gcutil 11-printcompilation

jstat的输出

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

1~ ᐅ jstat -gcutil 692 1000 2 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 3 0.00 41.49 59.80 83.66 89.92 78.74 295 5.436 10 3.855 9.291 4 0.00 41.49 59.80 83.66 89.92 78.74 295 5.436 10 3.855 9.291 5 0.00 41.49 59.80 83.66 89.92 78.74 295 5.436 10 3.855 9.291 6 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

1option gcutil { 2 column { 3 header "^S0^" /* Survivor 0 Space - Percent Used */ 4 data (1-((sun.gc.generation.0.space.1.capacity - sun.gc.generation.0.space.1.used)/sun.gc.generation.0.space.1.capacity)) * 100 5 scale raw 6 align right 7 width 6 8 format "0.00" 9 } 10 column { 11 header "^S1^" /* Survivor 1 Space - Percent Used */ 12 data (1-((sun.gc.generation.0.space.2.capacity - sun.gc.generation.0.space.2.used)/sun.gc.generation.0.space.2.capacity)) * 100 13 scale raw 14 align right 15 width 6 16 format "0.00" 17 } 18 column { 19 header "^E^" /* Eden Space - Percent Used */ 20 data (1-((sun.gc.generation.0.space.0.capacity - sun.gc.generation.0.space.0.used)/sun.gc.generation.0.space.0.capacity)) * 100 21 align right 22 scale raw 23 width 6 24 format "0.00" 25 } 26 column { 27 header "^O^" /* Old Space - Percent Used */ 28 data (1-((sun.gc.generation.1.space.0.capacity - sun.gc.generation.1.space.0.used)/sun.gc.generation.1.space.0.capacity)) * 100 29 align right 30 scale raw 31 width 6 32 format "0.00" 33 } 34 column { 35 header "^M^" /* Metaspace Space - Percent Used */ 36 data (1-((sun.gc.metaspace.capacity - sun.gc.metaspace.used)/sun.gc.metaspace.capacity)) * 100 37 align right 38 width 6 39 scale raw 40 format "0.00" 41 } 42 column { 43 header "^CCS^" /* Compressed Class Space Space - Percent Used */ 44 data (1-((sun.gc.compressedclassspace.capacity - sun.gc.compressedclassspace.used)/sun.gc.compressedclassspace.capacity)) * 100 45 align right 46 width 6 47 scale raw 48 format "0.00" 49 } 50 column { 51 header "^YGC^" /* Young Generation Collections */ 52 data sun.gc.collector.0.invocations 53 align right 54 width 6 55 format "0" 56 } 57 column { 58 header "^YGCT^" /* Young Generation Collection Time */ 59 data sun.gc.collector.0.time/sun.os.hrt.frequency 60 align right 61 scale sec 62 width 8 63 format "0.000" 64 } 65 column { 66 header "^FGC^" /* Full Collections */ 67 data sun.gc.collector.1.invocations 68 align right 69 width 5 70 scale raw 71 format "0" 72 } 73 column { 74 header "^FGCT^" /* Full Collection Time */ 75 data sun.gc.collector.1.time/sun.os.hrt.frequency 76 align right 77 scale sec 78 width 8 79 format "0.000" 80 } 81 column { 82 header "^GCT^" /* Total Garbage Collection Time */ 83 data (sun.gc.collector.0.time + sun.gc.collector.1.time)/sun.os.hrt.frequency 84 align right 85 width 8 86 scale sec 87 format "0.000" 88 } 89}

从上面的定义我们知道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都不会被创建,jvm运行过程中自然不会再将这些性能数据保存起来,默认情况是是打开的

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

具体代码在PerfMemory::create_memory_region里

1 if (PerfDisableSharedMem) { // do not share the memory for the performance data. 2 _start = create_standard_memory(size); 3 } else { 4 _start = create_shared_memory(size); if (_start == NULL) { // creation of the shared memory region failed, attempt 5 // to create a contiguous, non-shared memory region instead. 6 // 7 if (PrintMiscellaneous && Verbose) { 8 warning("Reverting to non-shared PerfMemory region.\n"); 9 } 10 PerfDisableSharedMem = true; 11 _start = create_standard_memory(size); 12 } 13 }

文件删除

那这个文件什么时候删除?正常情况下当进程退出的时候会自动删除,但是某些极端情况下,比如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文件的路径

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

欢迎关注 PerfMa 社区,推荐阅读

一个 JVM 参数引发的频繁 CMS GC

大数据中台之Kafka,到底好在哪里?

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid