本文基于openjdk11及hotspot
从Java8开始,JVM中的永久代被替换为了metaspace,本文将根据JVM源码对metaspace的初始化、分配内存、释放内存三个主要过程进行解析。
1. 数据结构
在metaspace中有如下一些概念,metaspace、classLoaderMetaspace、virtualSpace、metachunk、chunkManager、spaceManager、metablock。首先来看看各个数据结构中的内容,
1 2 3 4 5 6 7 8
| 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是全局共享的。
space_list和class_space_list的区别
这两者分别对应了一片内存区域,从名称中可以看出class_space_list是用来存储java中class的数据的。但事实上,不完全正确,只有当压缩指针生效的时候,class_space_list才会存在,否则class数据也同样会存储在space_list中。也就是说其实JVM的metaspace区域其实分为两块——Class区域和NonClass区域。
同理,chunk_manager_metadata对应了NonClass,chunk_manager_class对应了Class。
在Java中,每个ClassLoader实例(包括bootstrapClassLoader)都会在metaspace中拥有一块独立的区域,叫做classLoaderMetaspace。classLoaderMetaspace的数据结构如下:
1 2 3 4
| class ClassLoaderMetaspace : public CHeapObj<mtClass> { metaspace::SpaceManager* _vsm; metaspace::SpaceManager* _class_vsm; }
|
每个ClassLoaderMetaspace实例都会有一个spaceManager(可能还有一个classSpaceManager),用来处理ClassLoaderMetaspace的内存分配。
classLoaderMetaspace有些多种类型,分别对应了不同的ClassLoader
名称 |
对应ClassLoader |
StandardMetaspace |
普通ClassLoader |
BootMetaspace |
BootstrapClassLoader |
AnonymousMetaspace |
匿名ClassLoader |
ReflectionMetaspace |
反射ClassLoader |
不同类型的metaspace之间区别不大,主要在于他们创建的chunk大小的区别。
1.3 virtualSpace
virtualSpace组成了为metaspace分配的空间,以链表形式共享给ClassLoaderMetaspace使用。数据结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class VirtualSpace { char* _low_boundary; char* _high_boundary;
char* _low; char* _high; char* _lower_high; char* _middle_high; char* _upper_high;
char* _lower_high_boundary; char* _middle_high_boundary; char* _upper_high_boundary; }
|
在virtualSpace中划分为了上中下三个区域,如下图所示
1 2 3 4 5 6 7 8 9 10 11 12 13
| ----------------- 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 |
无固定大小 |
无固定大小 |
1.5 chunkManager
chunkManager用来那些已经释放了的chunk,用以重复使用,数据结构如下:
1 2 3 4
| class ChunkManager : public CHeapObj<mtInternal> { ChunkList _free_chunks[NumberOfFreeLists]; ChunkTreeDictionary _humongous_dictionary; }
|
其中free_chunks[]
用来存储special、small、medium三种类型的chunk,而humongous_dictionary
用来存储humongous类型的chunk。前面三种是固定大小,因此直接使用数组存储,而humongous是无固定大小的,因此使用排序二叉树的形式存储。
1.6 spaceManager
每个classLoaderMetaspace都对应一个NonClassSpaceManager和一个ClassSpaceManager,SpaceManager中存储了当前classLoaderMetaspace所使用的chunk的信息以及释放后用于重新使用的metablock列表。同时classLoaderMetaspace的内存分配最终也是由spaceManager来处理的。主要数据结构如下:
1 2 3 4 5
| class SpaceManager : public CHeapObj<mtClass> { Metachunk* _chunk_list; Metachunk* _current_chunk; BlockFreelist* _block_freelists; }
|
metablock则是由metachunk中分配出来用于最终使用的内存。在spaceManager的BlockFreeList中存储了那些释放后可再次使用的block。
1.8 总图
2. 初始化过程
JVM metaspace初始化分为了metaspace和classLoaderMetaspace的初始化。我们依次来看这两者的初始化,
metaspace的初始化分为三步,先是Arguments::apply_ergo()时调用Metaspace::ergo_initialize(),接着在universe_init()时调用Metaspace::global_initialize(),最后调用Metaspace::post_initialize()。这三步都是在JVM初始化的过程中执行。我们依次来看这三步初始化过程,
ergo_initialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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)。
global_initialize
全局初始化主要是用来初始化VirtualSpaceList和ChunkManager。其中ClassVirtualSpaceList的首节点大小直接分配为CompressedClassSpaceSize(不考虑开启UseSharedSpaces模式的情况下)。而NonClassVirtualSpaceList的首节点大小则分配为4M*8/2(64位机器)或 2200K/4*2(32位机器)。源码中有很多关于对齐计算的源码,较为啰嗦,此处就不展示了。
post_initialize
1 2 3
| void Metaspace::post_initialize() { MetaspaceGC::post_initialize(); }
|
post初始化主要是用于MetaspaceGC的初始化,本文不关注Metaspace的GC,因此此部分也不进行探讨。
classLoaderMetaspace的初始化与metaspace的初始化不同,metaspace是在JVM启动的时候就已经初始化了,而classLoaderMetaspace的初始化则是当其对应的classLoader需要使用metaspace的时候才会进行初始化,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ClassLoaderMetaspace* ClassLoaderData::metaspace_non_null() { ClassLoaderMetaspace* metaspace = OrderAccess::load_acquire(&_metaspace); if (metaspace == NULL) { MutexLockerEx ml(_metaspace_lock, Mutex::_no_safepoint_check_flag); 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的初始化过程,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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); } }
|
在这段代码中我们可以看到初始化过程主要包含两个步骤,
- 创建NonClassSpaceManger(_vsm)和ClassSpaceManager(_class_vsm)
- 初始化第一个NonClassChunk和第一个ClassChunk
我们接下来重点关注一下第一个Chunk的初始化过程(简单期间,我们只关注NonClass类型的初始化,其实两者基本一样)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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_freelist中尝试分配一个chunk
- 从全局virtualSpaceList中创建一个新的chunk
- 将新的chunk添加到spaceManager中管理
不过在探究这三步之前,我们先来看看第一句代码,计算chunk大小,我们先来看看chunk大小如何计算,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| enum ChunkSizes { 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
方法,我们来看看具体源码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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三种大小的chunk
- humongous类型的chunk
其中specialized、small、medium三种类型的freeChunk分别对应了三个列表,而humongou类型的freeChunk由于其大小不固定,则使用排序二叉树来存储。
非humongou类型的chunk在分配过程中如果失败,会尝试将更大的chunk进行拆分。
接下来看从全局virtualSpaceList中创建一个新的chunk的过程,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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分配chunk
- 扩展virtualSpace
- 再次尝试从当前virtualSpace分配chunk
比较让人感到好奇的是第二步,扩展virtualSpace,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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; }
|
这一步主要包含几个核心步骤:
- 尝试扩展当前virtualSpace
- 当前virtualSpace不满足要求,则将当前virtualSpace退休。这一步会将当前virtualSpace的剩余空间回收到chunk_freelist
- 创建一个新的virtualSpace
总结
整个classLoaderMetaspace的初始化过程可以总结为如下步骤:
- 创建SpaceManager
- 初始化ClassLoaderMetaspace第一个Chunk
- 从全局chunk_freelist中尝试分配一个chunk
- 从全局virtualSpaceList中创建一个新的chunk
- 从当前virtualSpace尝试分配
- 回收当前virtualSpace剩余空间,新建一个virtualSpace并尝试分配
- 初始化完成,将chunk添加到spaceManager中管理
3. 分配内存
对于metaspace而言,除了初始化之外,还有两个最重要的功能——分配内存和释放内存。我们先来看分配内存,
1 2 3 4 5 6 7 8 9 10 11
| 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)
方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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同样也是分为了小的和大的,数据结构如下:
1 2 3 4 5 6 7 8
| 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进行分配。
4. 释放内存
释放内存的代码则比较简单,即直接将需要释放的内存放回block_freelist中重新使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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部分的初始化,内存分配,内存释放便已结束。