在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的功能,這些數據可以在咱們排查性能問題、處理故障時發揮巨大做用。操作系統