在Java内置工具包tools.jar(一)sun.tools.jstack.Jps中咱们谈到,jps工具的数据来源自hsperfdata_xxx目录下的文件中。其实,除了jps还有jstat以及基于此命令之上提供的jstatd、visualgc工具,数据来源均基于JVM Performance Counters输出的文件中,在JVM中该文件被称为PerfData。PerfData中记录了JVM运行时刻gc、编译器、classLoder、操做系统、线程等的状态数据,对于分析jvm运行状态很是重要。
从头开始,在JVM初始化函数中,咱们看到初始化全局数据中执行了perfMemory_init()方法:html
void vm_init_globals() { check_ThreadShadow(); basic_types_init(); eventlog_init(); mutex_init(); chunkpool_init(); perfMemory_init(); }
void perfMemory_init() { if (!UsePerfData) return; PerfMemory::initialize(); }
// create the PerfData memory region // // This method creates the memory region used to store performance // data for the JVM. The memory may be created in standard or // shared memory. // void PerfMemory::create_memory_region(size_t size) { 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); } } if (_start != NULL) _capacity = size; }
也就是PerfData的初始化,其中UsePerfData控制了是否使用该特性。java
-XX:+UsePerfData:Enables the perfdata
feature. This option is enabled by default to allow JVM monitoring and performance testing. Disabling it suppresses the creation of the hsperfdata_userid
directories. To disable the perfdata
feature, specify -XX:-UsePerfData
.jvm
PerfMemory的初始化在不一样的操做系统有不一样的实现,咱们仍以Linux为例,jvm须要申请一分内存在存储这份数据,从代码中能够看到咱们有两种选择:create_shared_memory 和create_standard_memory,共享模式的内存即便用文件hsperfdata_xxx来给不一样的用户/进程来读取,该文件同时使用mmap的方式将内容映射到内存中。私有模式即申请普通内存,此时外部进程没法读取内存数据,因此基于此数据的命令都将失效(jps/jstat等),另外须要注意的是在共享模式申请内存(同时能够理解为建立文件)失败时会转换为私有模式。这里又来了一个JVM参数PerfDisableSharedMem,默认咱们使用的jdk都是false使用共享模式,但咱们仍可使用参数来控制这个行为。函数
product(bool, PerfDisableSharedMem, false,"Store performance data in standard memory")
那么,存储性能数据的内存空间已经申请完毕,接下来如何收集数据?Performance Counters定义了一些它数据来源的nameSpace,以下代码所示。工具
/* jvmstat global and subsystem counter name space - enumeration value * serve as an index into the PerfDataManager::_name_space[] array * containing the corresponding name space string. Only the top level * subsystem name spaces are represented here. */ enum CounterNS { // top level name spaces JAVA_NS, COM_NS, SUN_NS, // subsystem name spaces JAVA_GC, // Garbage Collection name spaces COM_GC, SUN_GC, JAVA_CI, // Compiler name spaces COM_CI, SUN_CI, JAVA_CLS, // Class Loader name spaces COM_CLS, SUN_CLS, JAVA_RT, // Runtime name spaces COM_RT, SUN_RT, JAVA_OS, // Operating System name spaces COM_OS, SUN_OS, JAVA_THREADS, // Threads System name spaces COM_THREADS, SUN_THREADS, JAVA_PROPERTY, // Java Property name spaces COM_PROPERTY, SUN_PROPERTY, NULL_NS, COUNTERNS_LAST = NULL_NS };
在JVM运行期间,经过调用PerfDataManager保持时刻记录这些运行数据,它提供一系列的接口供收集性能数据,咱们以查找调用方的方式能够看到StatSampler是触发数据收集的函数,它在Thread::create_vm()时建立的一个守护线程,在engage()中看到它的初始化伴随一个JVM参数PerfDataSamplingInterval:默认50秒执行一次去counter数据。性能
product(intx, PerfDataSamplingInterval, 50 /*ms*/,"Data sampling interval in milliseconds")
至此,性能数据就源源不断的采集下来,咱们回顾一下整个流程:spa
一、启动jvm,初始化perfData内存。包含共享内存和普通内存申请。共享内存涉及映射文件的建立位置等逻辑。 二、一个线程启动,均会初始化StatSampler,以PerfDataSamplingInterval的频率采集数据至perfData中。 三、perfData汇总数据存储在内存中,在共享内存模式下以mmap方式映射到文件中。 四、文件可被其余进程/用户读取。
以数据流的方式来理解,其实并无什么高深之处,数据的收集须要各个运行模块配合采集。这样就完成了Performance Counters的功能,这些数据可以在咱们排查性能问题、处理故障时发挥巨大做用。操作系统