本文基於openjdk11及hotspot
java
從Java8開始,JVM中的永久代被替換爲了metaspace,本文將根據JVM源碼對metaspace的初始化、分配內存、釋放內存三個主要過程進行解析。node
在metaspace中有以下一些概念,metaspace、classLoaderMetaspace、virtualSpace、metachunk、chunkManager、spaceManager、metablock。首先來看看各個數據結構中的內容,bootstrap
// hotspot/share/memory/metaspace.hpp
class Metaspace : public AllStatic {
static metaspace::VirtualSpaceList* _space_list;
static metaspace::VirtualSpaceList* _class_space_list;
static metaspace::ChunkManager* _chunk_manager_metadata;
static metaspace::ChunkManager* _chunk_manager_class;
}
複製代碼
Metaspace是一個只包含靜態屬性和靜態方法的類,看上去更像是一個工具類。在裏面重要包含了VirtualSpaceList和ChunkManager,不難看出VirtualSpaceList及ChunkManager是全局共享的。數組
這二者分別對應了一片內存區域,從名稱中能夠看出class_space_list是用來存儲java中class的數據的。但事實上,不徹底正確,只有當壓縮指針生效的時候,class_space_list纔會存在,不然class數據也一樣會存儲在space_list中。也就是說其實JVM的metaspace區域其實分爲兩塊——Class區域和NonClass區域。bash
同理,chunk_manager_metadata對應了NonClass,chunk_manager_class對應了Class。數據結構
在Java中,每一個ClassLoader實例(包括bootstrapClassLoader)都會在metaspace中擁有一塊獨立的區域,叫作classLoaderMetaspace。classLoaderMetaspace的數據結構以下:app
class ClassLoaderMetaspace : public CHeapObj<mtClass> {
metaspace::SpaceManager* _vsm;
metaspace::SpaceManager* _class_vsm;
}
複製代碼
每一個ClassLoaderMetaspace實例都會有一個spaceManager(可能還有一個classSpaceManager),用來處理ClassLoaderMetaspace的內存分配。工具
classLoaderMetaspace有些多種類型,分別對應了不一樣的ClassLoaderpost
名稱 | 對應ClassLoader |
---|---|
StandardMetaspace | 普通ClassLoader |
BootMetaspace | BootstrapClassLoader |
AnonymousMetaspace | 匿名ClassLoader |
ReflectionMetaspace | 反射ClassLoader |
不一樣類型的metaspace之間區別不大,主要在於他們建立的chunk大小的區別。ui
virtualSpace組成了爲metaspace分配的空間,以鏈表形式共享給ClassLoaderMetaspace使用。數據結構以下:
class VirtualSpace {
// Reserved area
char* _low_boundary;
char* _high_boundary;
// Committed area
char* _low;
char* _high;
// MPSS Support
char* _lower_high;
char* _middle_high;
char* _upper_high;
char* _lower_high_boundary;
char* _middle_high_boundary;
char* _upper_high_boundary;
}
複製代碼
在virtualSpace中劃分爲了上中下三個區域,以下圖所示
----------------- upper_high_boundary / high_boundary
| unused | |
|--------| 上 |- upper_high
| used | |
----------------- middle_high_boundary
| unused | |
|--------| 中 |- middle_high
| used | |
----------------- lower_high_boundary
| unused | |
|--------| 下 |- lower_high
| used | |
----------------- low_boundary
複製代碼
這三塊區域的區別,本文不予細究。
metachunk是ClassLoaderMetaspace從VirtualSpace區域分配出來的內存,每一個ClassLoaderMetaspace都會經過spaceManager持有一個metachunk列表,代表它全部持有的metaspace內存,一樣的該classLoader的全部內存申請也所有是在chunk中進行。
在JVM中chunk從小到大分爲了四種類型,以及其對應的chunk大小以下表,
chunk類型 | Class(單位:字) | NonClass(單位:字) |
---|---|---|
specialized | 128 | 128 |
small | 256 | 512 |
medium | 4K | 8K |
humongous | 無固定大小 | 無固定大小 |
chunkManager用來那些已經釋放了的chunk,用以重複使用,數據結構以下:
class ChunkManager : public CHeapObj<mtInternal> {
ChunkList _free_chunks[NumberOfFreeLists];
ChunkTreeDictionary _humongous_dictionary;
}
複製代碼
其中free_chunks[]
用來存儲special、small、medium三種類型的chunk,而humongous_dictionary
用來存儲humongous類型的chunk。前面三種是固定大小,所以直接使用數組存儲,而humongous是無固定大小的,所以使用排序二叉樹的形式存儲。
每一個classLoaderMetaspace都對應一個NonClassSpaceManager和一個ClassSpaceManager,SpaceManager中存儲了當前classLoaderMetaspace所使用的chunk的信息以及釋放後用於從新使用的metablock列表。同時classLoaderMetaspace的內存分配最終也是由spaceManager來處理的。主要數據結構以下:
class SpaceManager : public CHeapObj<mtClass> {
Metachunk* _chunk_list;
Metachunk* _current_chunk;
BlockFreelist* _block_freelists;
}
複製代碼
metablock則是由metachunk中分配出來用於最終使用的內存。在spaceManager的BlockFreeList中存儲了那些釋放後可再次使用的block。
JVM metaspace初始化分爲了metaspace和classLoaderMetaspace的初始化。咱們依次來看這二者的初始化,
metaspace的初始化分爲三步,先是Arguments::apply_ergo()時調用Metaspace::ergo_initialize(),接着在universe_init()時調用Metaspace::global_initialize(),最後調用Metaspace::post_initialize()。這三步都是在JVM初始化的過程當中執行。咱們依次來看這三步初始化過程,
void Metaspace::ergo_initialize() {
if (DumpSharedSpaces) {
FLAG_SET_ERGO(bool, UseLargePagesInMetaspace, false);
}
size_t page_size = os::vm_page_size();
if (UseLargePages && UseLargePagesInMetaspace) {
page_size = os::large_page_size();
}
_commit_alignment = page_size;
_reserve_alignment = MAX2(page_size, (size_t)os::vm_allocation_granularity());
MaxMetaspaceSize = align_down_bounded(MaxMetaspaceSize, _reserve_alignment);
if (MetaspaceSize > MaxMetaspaceSize) {
MetaspaceSize = MaxMetaspaceSize;
}
MetaspaceSize = align_down_bounded(MetaspaceSize, _commit_alignment);
assert(MetaspaceSize <= MaxMetaspaceSize, "MetaspaceSize should be limited by MaxMetaspaceSize");
MinMetaspaceExpansion = align_down_bounded(MinMetaspaceExpansion, _commit_alignment);
MaxMetaspaceExpansion = align_down_bounded(MaxMetaspaceExpansion, _commit_alignment);
CompressedClassSpaceSize = align_down_bounded(CompressedClassSpaceSize, _reserve_alignment);
size_t min_metaspace_sz =
VIRTUALSPACEMULTIPLIER * InitialBootClassLoaderMetaspaceSize;
if (UseCompressedClassPointers) {
if ((min_metaspace_sz + CompressedClassSpaceSize) > MaxMetaspaceSize) {
if (min_metaspace_sz >= MaxMetaspaceSize) {
vm_exit_during_initialization("MaxMetaspaceSize is too small.");
} else {
FLAG_SET_ERGO(size_t, CompressedClassSpaceSize,
MaxMetaspaceSize - min_metaspace_sz);
}
}
} else if (min_metaspace_sz >= MaxMetaspaceSize) {
FLAG_SET_ERGO(size_t, InitialBootClassLoaderMetaspaceSize,
min_metaspace_sz);
}
set_compressed_class_space_size(CompressedClassSpaceSize);
}
複製代碼
在ergo初始化過程當中主要是進行一些全局變量的設置,例如MaxMetaspaceSize、MinMetaspaceExpansion、MaxMetaspaceExpansion和CompressedClassSpaceSize。其中比較重要的就是MaxMetaspaceSize和CompressedClassSpaceSize,默認狀況下CompressedClassSpaceSize的大小爲1G(相見globals.hpp)。
全局初始化主要是用來初始化VirtualSpaceList和ChunkManager。其中ClassVirtualSpaceList的首節點大小直接分配爲CompressedClassSpaceSize(不考慮開啓UseSharedSpaces模式的狀況下)。而NonClassVirtualSpaceList的首節點大小則分配爲4M*8/2(64位機器)或 2200K/4*2(32位機器)。源碼中有不少關於對齊計算的源碼,較爲囉嗦,此處就不展現了。
void Metaspace::post_initialize() {
MetaspaceGC::post_initialize();
}
複製代碼
post初始化主要是用於MetaspaceGC的初始化,本文不關注Metaspace的GC,所以此部分也不進行探討。
classLoaderMetaspace的初始化與metaspace的初始化不一樣,metaspace是在JVM啓動的時候就已經初始化了,而classLoaderMetaspace的初始化則是當其對應的classLoader須要使用metaspace的時候纔會進行初始化,代碼以下:
ClassLoaderMetaspace* ClassLoaderData::metaspace_non_null() {
ClassLoaderMetaspace* metaspace = OrderAccess::load_acquire(&_metaspace);
if (metaspace == NULL) {
MutexLockerEx ml(_metaspace_lock, Mutex::_no_safepoint_check_flag);
// Check if _metaspace got allocated while we were waiting for this lock.
if ((metaspace = _metaspace) == NULL) {
if (this == the_null_class_loader_data()) {
assert (class_loader() == NULL, "Must be");
metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::BootMetaspaceType);
} else if (is_anonymous()) {
metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::AnonymousMetaspaceType);
} else if (class_loader()->is_a(SystemDictionary::reflect_DelegatingClassLoader_klass())) {
metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::ReflectionMetaspaceType);
} else {
metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::StandardMetaspaceType);
}
OrderAccess::release_store(&_metaspace, metaspace);
}
}
return metaspace;
}
複製代碼
在這段代碼中咱們能夠看到四種ClassLoaderMetaspace類型分別與四種ClassLoader一一對應。
接下來是classLoaderMetaspace的初始化過程,
void ClassLoaderMetaspace::initialize(Mutex* lock, Metaspace::MetaspaceType type) {
Metaspace::verify_global_initialization();
DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_metaspace_births));
_vsm = new SpaceManager(Metaspace::NonClassType, type, lock)
if (Metaspace::using_class_space()) {
_class_vsm = new SpaceManager(Metaspace::ClassType, type, lock);
}
MutexLockerEx cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
initialize_first_chunk(type, Metaspace::NonClassType);
if (Metaspace::using_class_space()) {
initialize_first_chunk(type, Metaspace::ClassType);
}
}
複製代碼
在這段代碼中咱們能夠看到初始化過程主要包含兩個步驟,
咱們接下來重點關注一下第一個Chunk的初始化過程(簡單期間,咱們只關注NonClass類型的初始化,其實二者基本同樣)。
// 代碼已通過簡單整理
void ClassLoaderMetaspace::initialize_first_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype) {
size_t chunk_word_size = get_space_manager(mdtype)->get_initial_chunk_size(type);
Metachunk* chunk = Metaspace::get_chunk_manager(mdtype)->chunk_freelist_allocate(chunk_word_size);
if (chunk == NULL) {
chunk = Metaspace::get_space_list(mdtype)->get_new_chunk(chunk_word_size,
get_space_manager(mdtype)->medium_chunk_bunch());
}
if (chunk != NULL) {
get_space_manager(mdtype)->add_chunk(chunk, true);
}
}
複製代碼
整體看來,初始化第一個chunk分爲了三步:
不過在探究這三步以前,咱們先來看看第一句代碼,計算chunk大小,咱們先來看看chunk大小如何計算,
enum ChunkSizes { // in words.
ClassSpecializedChunk = 128,
SpecializedChunk = 128,
ClassSmallChunk = 256,
SmallChunk = 512,
ClassMediumChunk = 4 * K,
MediumChunk = 8 * K
};
size_t SpaceManager::adjust_initial_chunk_size(size_t requested, bool is_class_space) {
size_t chunk_sizes[] = {
specialized_chunk_size(is_class_space),
small_chunk_size(is_class_space),
medium_chunk_size(is_class_space)
};
for (size_t i = 0; i < ARRAY_SIZE(chunk_sizes); i++) {
if (requested <= chunk_sizes[i]) {
return chunk_sizes[i];
}
}
return requested;
}
size_t SpaceManager::get_initial_chunk_size(Metaspace::MetaspaceType type) const {
size_t requested;
if (is_class()) {
switch (type) {
case Metaspace::BootMetaspaceType: requested = Metaspace::first_class_chunk_word_size(); break;
case Metaspace::AnonymousMetaspaceType: requested = ClassSpecializedChunk; break;
case Metaspace::ReflectionMetaspaceType: requested = ClassSpecializedChunk; break;
default: requested = ClassSmallChunk; break;
}
} else {
switch (type) {
case Metaspace::BootMetaspaceType: requested = Metaspace::first_chunk_word_size(); break;
case Metaspace::AnonymousMetaspaceType: requested = SpecializedChunk; break;
case Metaspace::ReflectionMetaspaceType: requested = SpecializedChunk; break;
default: requested = SmallChunk; break;
}
}
const size_t adjusted = adjust_initial_chunk_size(requested);
assert(adjusted != 0, "Incorrect initial chunk size. Requested: "
SIZE_FORMAT " adjusted: " SIZE_FORMAT, requested, adjusted);
return adjusted;
}
複製代碼
在這裏咱們能夠看到不一樣類型的classLoaderMetaspace之間的區別,它們的初始chunk大小是不同的。同時,對於Class類和NonClass類型的Chunk,它們的specialized、small、medium三檔的大小值也是徹底不一樣的。
接下來,咱們重點仍然放回第一個chunk的初始化過程,此處重點關注前兩步,先是第一步——從全局chunk_freelist中嘗試分配一個chunk。ChunkManager::chunk_freelist_allocate(size_t word_size)
中主要調用了ChunkManager::free_chunks_get
方法,咱們來看看具體源碼,
// 去除了校驗代碼&日誌代碼
Metachunk* ChunkManager::free_chunks_get(size_t word_size) {
slow_locked_verify();
Metachunk* chunk = NULL;
bool we_did_split_a_chunk = false;
if (list_index(word_size) != HumongousIndex) {
ChunkList* free_list = find_free_chunks_list(word_size);
chunk = free_list->head();
if (chunk == NULL) {
ChunkIndex target_chunk_index = get_chunk_type_by_size(word_size, is_class());
Metachunk* larger_chunk = NULL;
ChunkIndex larger_chunk_index = next_chunk_index(target_chunk_index);
while (larger_chunk == NULL && larger_chunk_index < NumberOfFreeLists) {
larger_chunk = free_chunks(larger_chunk_index)->head();
if (larger_chunk == NULL) {
larger_chunk_index = next_chunk_index(larger_chunk_index);
}
}
if (larger_chunk != NULL) {
chunk = split_chunk(word_size, larger_chunk);
we_did_split_a_chunk = true;
}
}
if (chunk == NULL) {
return NULL;
}
free_list->remove_chunk(chunk)
} else {
chunk = humongous_dictionary()->get_chunk(word_size);
if (chunk == NULL) {
return NULL;
}
}
chunk->set_next(NULL);
chunk->set_prev(NULL);
return chunk;
}
複製代碼
簡單講解一下這段代碼,內存分配分爲了兩種狀況
其中specialized、small、medium三種類型的freeChunk分別對應了三個列表,而humongou類型的freeChunk因爲其大小不固定,則使用排序二叉樹來存儲。
非humongou類型的chunk在分配過程當中若是失敗,會嘗試將更大的chunk進行拆分。
接下來看從全局virtualSpaceList中建立一個新的chunk的過程,
Metachunk* VirtualSpaceList::get_new_chunk(size_t chunk_word_size, size_t suggested_commit_granularity) {
Metachunk* next = current_virtual_space()->get_chunk_vs(chunk_word_size);
if (next != NULL) {
return next;
}
const size_t size_for_padding = largest_possible_padding_size_for_chunk(chunk_word_size, this->is_class());
size_t min_word_size = align_up(chunk_word_size + size_for_padding, Metaspace::commit_alignment_words());
size_t preferred_word_size = align_up(suggested_commit_granularity, Metaspace::commit_alignment_words());
if (min_word_size >= preferred_word_size) {
preferred_word_size = min_word_size;
}
bool expanded = expand_by(min_word_size, preferred_word_size);
if (expanded) {
next = current_virtual_space()->get_chunk_vs(chunk_word_size);
}
return next;
}
複製代碼
整段代碼能夠整理爲三步:
比較讓人感到好奇的是第二步,擴展virtualSpace,
bool VirtualSpaceList::expand_by(size_t min_words, size_t preferred_words) {
if (!MetaspaceGC::can_expand(min_words, this->is_class())) {
return false;
}
size_t allowed_expansion_words = MetaspaceGC::allowed_expansion();
if (allowed_expansion_words < min_words) {
return false;
}
size_t max_expansion_words = MIN2(preferred_words, allowed_expansion_words);
bool vs_expanded = expand_node_by(current_virtual_space(), min_words, max_expansion_words);
if (vs_expanded) {
return true;
}
retire_current_virtual_space();
size_t grow_vs_words = MAX2((size_t)VirtualSpaceSize, preferred_words);
grow_vs_words = align_up(grow_vs_words, Metaspace::reserve_alignment_words());
if (create_new_virtual_space(grow_vs_words)) {
if (current_virtual_space()->is_pre_committed()) {
return true;
}
return expand_node_by(current_virtual_space(), min_words, max_expansion_words);
}
return false;
}
複製代碼
這一步主要包含幾個核心步驟:
整個classLoaderMetaspace的初始化過程能夠總結爲以下步驟:
對於metaspace而言,除了初始化以外,還有兩個最重要的功能——分配內存和釋放內存。咱們先來看分配內存,
static bool is_class_space_allocation(MetadataType mdType) {
return mdType == ClassType && using_class_space();
}
MetaWord* ClassLoaderMetaspace::allocate(size_t word_size, Metaspace::MetadataType mdtype) {
if (Metaspace::is_class_space_allocation(mdtype)) {
return class_vsm()->allocate(word_size);
} else {
return vsm()->allocate(word_size);
}
}
複製代碼
這段代碼中,咱們能夠看到,只有元數據類型爲Class類型以及使用壓縮指針的時候纔會使用Class空間,不然都是使用NonClass空間。
接下來,咱們繼續探究vsm()->allocate(word_size)
方法,
MetaWord* SpaceManager::allocate(size_t word_size) {
MutexLockerEx cl(lock(), Mutex::_no_safepoint_check_flag);
size_t raw_word_size = get_allocation_word_size(word_size);
BlockFreelist* fl = block_freelists();
MetaWord* p = NULL;
if (fl != NULL && fl->total_size() > allocation_from_dictionary_limit) {
p = fl->get_block(raw_word_size);
}
if (p == NULL) {
p = allocate_work(raw_word_size);
}
return p;
}
複製代碼
在這一步與以前metaspace初始化chunk有些殊途同歸之處,此處也是先嚐試從block_freelists中進行分配,分配失敗再嘗試從chunk中進行分配,邏輯幾乎與上文的chunk初始化一摸同樣。
block_freelists一樣也是分爲了小的和大的,數據結構以下:
class BlockFreelist : public CHeapObj<mtClass> {
BlockTreeDictionary* const _dictionary;
SmallBlocks* _small_blocks;
}
class SmallBlocks : public CHeapObj<mtClass> {
FreeList<Metablock> _small_lists[_small_block_max_size - _small_block_min_size];
}
複製代碼
在small_block_max_size到small_block_min_size範圍內的block經過鏈表來存儲,更大的block則使用排序二叉樹來實現。
至於chunk分配內存也一模一樣,先嚐試從當前chunk分配,分配失敗再新建chunk進行分配。
釋放內存的代碼則比較簡單,即直接將須要釋放的內存放回block_freelist中從新使用。
void SpaceManager::deallocate(MetaWord* p, size_t word_size) {
size_t raw_word_size = get_allocation_word_size(word_size);
if (block_freelists() == NULL) {
_block_freelists = new BlockFreelist();
}
block_freelists()->return_block(p, raw_word_size);
}
void BlockFreelist::return_block(MetaWord* p, size_t word_size) {
Metablock* free_chunk = ::new (p) Metablock(word_size);
if (word_size < SmallBlocks::small_block_max_size()) {
small_blocks()->return_block(free_chunk, word_size);
} else {
dictionary()->return_chunk(free_chunk);
}
}
複製代碼
至此,metaspace部分的初始化,內存分配,內存釋放便已結束。