目录
  1. 1. 数据结构
    1. 1.1 metaspace
      1. space_list和class_space_list的区别
    2. 1.2 classLoaderMetaspace
      1. classLoaderMetaspace类型
    3. 1.3 virtualSpace
    4. 1.4 metachunk
    5. 1.5 chunkManager
    6. 1.6 spaceManager
    7. 1.7 metablock
    8. 1.8 总图
  2. 2. 初始化过程
    1. 2.1 metaspace初始化
      1. ergo_initialize
      2. global_initialize
      3. post_initialize
    2. 2.2 classLoaderMetaspace初始化
      1. 总结
  3. 3. 分配内存
  4. 4. 释放内存
Java metaspace源码解析

本文基于openjdk11及hotspot

从Java8开始,JVM中的永久代被替换为了metaspace,本文将根据JVM源码对metaspace的初始化、分配内存、释放内存三个主要过程进行解析。

1. 数据结构

在metaspace中有如下一些概念,metaspace、classLoaderMetaspace、virtualSpace、metachunk、chunkManager、spaceManager、metablock。首先来看看各个数据结构中的内容,

1.1 metaspace

1
2
3
4
5
6
7
8
// 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是全局共享的。

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。

1.2 classLoaderMetaspace

在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类型

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 {
// 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中划分为了上中下三个区域,如下图所示

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

这三块区域的区别,本文不予细究。

1.4 metachunk

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;
}

1.7 metablock

metablock则是由metachunk中分配出来用于最终使用的内存。在spaceManager的BlockFreeList中存储了那些释放后可再次使用的block。

1.8 总图

image-20190809164533218

2. 初始化过程

JVM metaspace初始化分为了metaspace和classLoaderMetaspace的初始化。我们依次来看这两者的初始化,

2.1 metaspace初始化

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,因此此部分也不进行探讨。

2.2 classLoaderMetaspace初始化

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);
// 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的初始化过程,

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);
}
}

在这段代码中我们可以看到初始化过程主要包含两个步骤,

  1. 创建NonClassSpaceManger(_vsm)和ClassSpaceManager(_class_vsm)
  2. 初始化第一个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分为了三步:

  1. 从全局chunk_freelist中尝试分配一个chunk
  2. 从全局virtualSpaceList中创建一个新的chunk
  3. 将新的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 {    // 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方法,我们来看看具体源码,

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;
}

整段代码可以整理为三步:

  1. 尝试从当前virtualSpace分配chunk
  2. 扩展virtualSpace
  3. 再次尝试从当前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的初始化过程可以总结为如下步骤:

  1. 创建SpaceManager
  2. 初始化ClassLoaderMetaspace第一个Chunk
    1. 从全局chunk_freelist中尝试分配一个chunk
    2. 从全局virtualSpaceList中创建一个新的chunk
      1. 从当前virtualSpace尝试分配
      2. 回收当前virtualSpace剩余空间,新建一个virtualSpace并尝试分配
    3. 初始化完成,将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部分的初始化,内存分配,内存释放便已结束。

文章作者: 谷河
文章链接: https://www.lyytaw.com/java/Java%20metaspace%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 谷河|BLOG