背景
看泉子的一篇文章:JVM原始碼分析之Jstat工具原理完全解讀 – 你假笨 裡提到了兩個JVM引數,可以控制perfdata檔案是否共享,取用泉子對這兩個引數的解釋:
UsePerfData:如果關閉了UsePerfData這個引數,那麼jvm啟動過程中perf memory都不會被建立,預設情況是是開啟的
PerfDisableSharedMem:該引數決定了儲存PerfData的記憶體是不是可以被共享,也就是說不管這個引數設定沒設定,jvm在啟動的時候都會分配一塊記憶體來存PerfData,只是說這個PerfData是不是其他行程可見的問題,如果設定了這個引數,說明不能被共享,此時其他行程將訪問不了該記憶體,這樣一來,譬如我們jps,jstat等都無法工作。預設這個引數是關閉的,也就是預設支援共享的方式
由於perfdata檔案時透過mmap共享的,因此考慮看下perfdata檔案的建立過程,看看跟mmap的MAPSHARED和MAPPRIVATE兩個標誌位是如何聯絡在一起的。perfdata檔案底層是使用mmap介面實現的,而mmap介面的引數中有關於記憶體可見性的兩個引數:MAPSHARED和MAPPRIVATE,如果JVM引數設定允許perfdata檔案共享,則使用MAP_SHARED標記。
原始碼分析
perfdata檔案在jvm啟動的時候創,在init.cpp檔案中:
void vm_init_globals() {
check_ThreadShadow();
basic_types_init();
eventlog_init();
mutex_init();
chunkpool_init();
perfMemory_init();
}
在perfMemory.cpp檔案中看下perfMemory_init()方法,
void perfMemory_init() {
if (!UsePerfData) return;
PerfMemory::initialize();
}
可以看出,如果UsePerfData引數設定為false,則直接傳回,不會建立perfdata檔案;接著看PerfMemory::initialize()方法,在這個方法裡會呼叫creatememoryregion(capacity);,用於申請perfdata的記憶體區域;creatememoryregion這個方法不同的平臺有不同的實現,我們這裡看linux平臺下的實現;
看下perfMemory_linux.cpp裡的程式碼,可以看到另一熟悉的JVM引數——PerfDisableSharedMem,如果這個引數設定為false,則會建立perfdata檔案,但是其他行程無法共享這塊記憶體,會導致jps、jstat等工具無法使用;
// 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;
}
首先看createsharedmemory(size)的實現:
createsharedmemory(size) ——> mmapcreateshared(size)——>mapAddress = (char)::mmap((char)0, size, PROTREAD|PROTWRITE, MAPSHARED, fd, 0);,在這裡看到了MAPSHARED標記。
然後看createstandardmemory(size)的實現,這裡並沒有跟之前猜想的一樣(用mmap方法建對映,傳入MAP_PRIVATE標記),而是使用了 os::reserve_memory(size)
方法,用來分配一段堆記憶體。
// Standard Memory Implementation Details
// create the PerfData memory region in standard memory.
//
static char* create_standard_memory(size_t size) {
// allocate an aligned chuck of memory
char* mapAddress = os::reserve_memory(size);
if (mapAddress == NULL) {
return NULL;
}
// commit memory
if (!os::commit_memory(mapAddress, size, !ExecMem)) {
if (PrintMiscellaneous && Verbose) {
warning("Could not commit PerfData memory\n");
}
os::release_memory(mapAddress, size);
return NULL;
}
return mapAddress;
}
至此可以確認兩個結論
-
建立shared記憶體,使用mmap,並傳入MAP_SHARED標記
-
建立standard記憶體,使用os::reserve_memory分配一段堆記憶體,這點跟之前猜想的不一樣,不過對這個方法還不是很熟悉,留著後面研究。
推薦一個我最近在學的JVM課程,來自Oracle高階研究員鄭宇迪在極客時間的JVM專欄,目前更新了16篇文章,我基本都跟下來了,質量值得信賴。
整個專欄將分為四大模組。
-
基本原理:剖析 Java 虛擬機器的執行機制,逐一介紹 Java 虛擬機器的設計決策以及工程實現;
-
高效實現:探索 Java 編譯器,以及內嵌於 Java 虛擬機器中的即時編譯器,幫助你更好地理解 Java 語言特性,繼而寫出簡潔高效的程式碼;
-
程式碼最佳化:介紹如何利用工具定位並解決程式碼中的問題,以及在已有工具不適用的情況下,如何打造專屬輪子;
-
虛擬機器黑科技:介紹甲骨文實驗室近年來的前沿工作之一 GraalVM。包括如何在 JVM 上高效執行其他語言;如何混搭這些語言,實現 Polyglot;如何將這些語言事前編譯(Ahead-Of-Time,AOT)成機器指令,單獨執行甚至嵌入至資料庫中執行。