概述
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 社区,推荐阅读