****本文不涉及GC,最多是一些个GC参数相关介绍。启动参数对元空间的影响CSDN一搜一大把,也不多做介绍。本文通过源码来详细介绍元空间的初始化、申请和使用,推荐一篇文章 翻译自OpenJDK Committer/Reviewer Thomas Stüfe的metaSpace
一、元空间初始化
先看下执行堆栈
Metaspace::global_initialize metaspace.cpp:3141
universe_init universe.cpp:655
init_globals init.cpp:104
Threads::create_vm thread.cpp:3417
JNI_CreateJavaVM jni.cpp:5207
InitializeJVM java.c:1214
JavaMain java.c:376
Metaspace::global_initialize 全局元空间初始化的函数入口,源码:
void Metaspace::global_initialize() {
MetaspaceGC::initialize();
...........................................................
if (DumpSharedSpaces) {//默认false
...........................................................
} else {
#if INCLUDE_CDS
..........................................................
if (UseSharedSpaces) {
...........................................................
}
#endif // INCLUDE_CDS
#ifdef _LP64
if (using_class_space()) {
if (UseSharedSpaces) {
...........................................................
} else {
char* base = (char*)align_ptr_up(Universe::heap()->reserved_region().end(), _reserve_alignment);
allocate_metaspace_compressed_klass_ptrs(base, 0);
}
}
#endif // _LP64
_first_chunk_word_size = InitialBootClassLoaderMetaspaceSize / BytesPerWord;
_first_chunk_word_size = align_word_size_up(_first_chunk_word_size);
_first_class_chunk_word_size = MIN2((size_t)MediumChunk*6, (CompressedClassSpaceSize/BytesPerWord)*2);
_first_class_chunk_word_size = align_word_size_up(_first_class_chunk_word_size);
size_t word_size = VIRTUALSPACEMULTIPLIER * _first_chunk_word_size;
word_size = align_size_up(word_size, Metaspace::reserve_alignment_words());
_space_list = new VirtualSpaceList(word_size);
_chunk_manager_metadata = new ChunkManager(SpecializedChunk, SmallChunk, MediumChunk);
if (!_space_list->initialization_succeeded()) {
vm_exit_during_initialization("Unable to setup metadata virtual space list.", NULL);
}
}
_tracer = new MetaspaceTracer();
}
不需要的代码 “…” 代替,剩下的一行行过
1.MetaspaceGC::initialize()
void MetaspaceGC::initialize() {
//初始值,没有太大作用,在Threads::create_vm函数中的会值重新赋值为MetaspaceSize
_capacity_until_GC = MaxMetaspaceSize;
}
//重新处置函数:
void MetaspaceGC::post_initialize() {
//借用一段PerfMa的博客:默认20.8M左右(x86下开启c2模式),主要是控制metaspaceGC发生的初始阈值,
//也是最小阈值,但是触发metaspaceGC的阈值是不断变化的,与之对比的主要是指
//Klass Metaspace与NoKlass Metaspace两块committed的内存和
//这里有两个信息:
//1、_capacity_until_GC是metaspaceGC发生的阈值
//2、MetaspaceSize是初始阈值、发生GC后_capacity_until_GC会变化,与MetaspaceSize大小无关了
_capacity_until_GC = MAX2(MetaspaceAux::committed_bytes(), MetaspaceSize);
}
2.using_class_space()
static bool using_class_space() {
return NOT_LP64(false) LP64_ONLY(UseCompressedClassPointers && !DumpSharedSpaces);
}
//元空间的klassType区是否使用取决于是否开启了指针压缩,默认true
3.char* base = (char*)align_ptr_up(Universe::heap()->reserved_region().end(), _reserve_alignment);
Universe::heap()->reserved_region().end()获取元数据区的起始地址
从这里可以看出指针压缩情况下元空间的classType区是紧挨着堆,classType区的起止于堆的申请相关。堆和classType的内存并不是os给定的堆空间,而是由mmap申请的文件或者其它对象映射,所以它们的首、尾地址完全可以自定义。
元空间预留的起始地址和堆的大小相关,先看ubuntu下分配堆的代码片段。
uintx
char* Universe::preferred_heap_base(size_t heap_size, size_t alignment, NARROW_OOP_MODE mode) {
//HeapBaseMinAddress表示Java堆的内存基地址,x86下默认是2G,将HeapBaseMinAddress按照alignment取整
uintx heap_base_min_address_aligned = align_size_up(HeapBaseMinAddress, alignment);
if (UseCompressedOops) {//开启指针压缩
const size_t total_size = heap_size + heap_base_min_address_aligned; //默认系统内存的1/4
// 根据不同的NARROW_OOP_MODE分别计算 Return specified base for the first request.
if (!FLAG_IS_DEFAULT(HeapBaseMinAddress) && (mode == UnscaledNarrowOop)) {//默认堆2G 应该没人用这么小的服务器了吧
base = heap_base_min_address_aligned; //heap_base_min_address_aligned = 2147483648(2G)16进制0x80000000
} else if ((total_size <= OopEncodingHeapMax) && (mode != HeapBasedNarrowOop)) { //Xmx +heap_base_min_address_aligned <= 32G
if ((total_size <= UnscaledOopHeapMax) && (mode == UnscaledNarrowOop) && (Universe::narrow_oop_shift() == 0)) {// 小于等于4G
base = (UnscaledOopHeapMax - heap_size); //UnscaledOopHeapMax = 4294967296(4G)16进制0x100000000
} else {
// 大于4G时
Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);
if (mode == UnscaledNarrowOop ||
mode == ZeroBasedNarrowOop && total_size <= UnscaledOopHeapMax) {
uint64_t heap_top = OopEncodingHeapMax; //OopEncodingHeapMax = 34,359,738,368(32G) 16进制0x800000000
if (UseCompressedClassPointers && !UseSharedSpaces &&
OopEncodingHeapMax <= 32*G) {
//CompressedClassSpaceSize = 1073741824 16进制0x400000000
uint64_t class_space = align_size_up(CompressedClassSpaceSize, alignment); //这里是开启指针压缩后,java类的压缩指针存放空间的初始大小。
uint64_t new_top = OopEncodingHeapMax-class_space; //去掉指针空间就是堆的空间
if (total_size <= new_top) { //小于32G
heap_top = new_top; //堆尾地址
}
}
// Align base to the adjusted top of the heap
base = heap_top - heap_size; //堆首地址
}
} else {
Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);
}
if ((base != 0) && ((base + heap_size) <= OopEncodingHeapMax)) { //小于32G
Universe::set_narrow_oop_base(NULL);
Universe::set_narrow_oop_use_implicit_null_checks(true);
} else {//Xmx < 32 && (Xmx + heap_base_min_address_aligned ) > 32G
Universe::set_narrow_oop_base((address)UnscaledOopHeapMax); // UnscaledOopHeapMax = 4294967296
#if defined(_WIN64) || defined(AIX)
if (UseLargePages) {
Universe::set_narrow_oop_use_implicit_null_checks(false);
}
#endif // _WIN64
}
}
#endif
return (char*)base; // also return NULL (don't care) for 32-bit VM
}
稍微展开分析,首先在Xmx小于32g的情况下元空间的classType区的分配
1.Xmx 小于等于2G, 元空间的classType起始地址的计算基数是4294967296,转换成16进制0x100000000这也是元空间的首地址。64位机下jvm的设定是八字节对齐,这意味着每个指针的后三位都是0是可忽略的。细数一下,0x100000000是个9位数的16进制。jvm最小对齐字节是4(也就是压缩量后的字节量)。结合这三点在看下0x100000000这个地址离四字节仅仅只差右移三位。
2.Xmx 大于2G,小于等于29G时元空间的压缩类空间起始地址的计算基数是34359738368,转成16进制0x8,0000,0000。以Xmx29G为例:heap_top = 0x8,0000,0000 - CompressedClassSpaceSize = 0x7C000,0000 为堆的尾地址。堆的基地址计算:heap_base = 0x7C000,0000 - Xmx = 00x8000,0000。heap_top 就是classType空间的基地址
3.Xmx 29G-30G与第二种分配大致一样,不同点在heap_top为0x8,0000,0000。
4.Xmx大于30G 小于32G。。。。。。。。。。算了没人那么极端吧,嗯~应该没有
-------------------------------------------------分割线------------------------------------------------
以下都以第二种2-29G分配堆,其他情况不说了分支有点多
----------------------------------------------------------------------------------------------------------
4.allocate_metaspace_compressed_klass_ptrs(base, 0)分配全局classType空间
先看代码
void Metaspace::allocate_metaspace_compressed_klass_ptrs(char* requested_addr, address cds_base) {
bool large_pages = false;
ReservedSpace metaspace_rs = ReservedSpace(compressed_class_space_size(),
_reserve_alignment,
large_pages,
requested_addr, 0);//requested_addr 全局元空间classType的起始位
if (!metaspace_rs.is_reserved()) {
#if INCLUDE_CDS
if (UseSharedSpaces) {
..............................................................
}
#endif
if (!metaspace_rs.is_reserved()) {
metaspace_rs = ReservedSpace(compressed_class_space_size(),
_reserve_alignment, large_pages);
if (!metaspace_rs.is_reserved()) {
vm_exit_during_initialization(err_msg("Could not allocate metaspace: %d bytes",
compressed_class_space_size()));
}
}
}
// If we got here then the metaspace got allocated.
MemTracker::record_virtual_memory_type((address)metaspace_rs.base(), mtClass);
#if INCLUDE_CDS
// Verify that we can use shared spaces. Otherwise, turn off CDS.
if (UseSharedSpaces && !can_use_cds_with_metaspace_addr(metaspace_rs.base(), cds_base)) {
FileMapInfo::stop_sharing_and_unmap("Could not allocate metaspace at a compatible address");
}
#endif
set_narrow_klass_base_and_shift((address)metaspace_rs.base(),
UseSharedSpaces ? (address)cds_base : 0);
initialize_class_space(metaspace_rs);
if (PrintCompressedOopsMode || (PrintMiscellaneous && Verbose)) {
...............................................
}
}
4.1 ReservedSpace申请内存,初始化第一块可使用的元空间
ReservedSpace metaspace_rs = ReservedSpace(compressed_class_space_size(),
_reserve_alignment,
large_pages,
requested_addr, 0);
ReservedSpace::ReservedSpace(size_t size, size_t alignment,
bool large,
char* requested_address,
const size_t noaccess_prefix) {
initialize(size+noaccess_prefix, alignment, large, requested_address,
noaccess_prefix, false);
}
void ReservedSpace::initialize(size_t size, size_t alignment, bool large,
char* requested_address,
const size_t noaccess_prefix,
bool executable) {
const size_t granularity = os::vm_allocation_granularity();
alignment = MAX2(alignment, (size_t)os::vm_page_size());
_base = NULL;
_size = 0;
_special = false;
_executable = executable;
_alignment = 0;
_noaccess_prefix = 0;
if (size == 0) {
return;
}
bool special = large && !os::can_commit_large_page_memory();
char* base = NULL;
if (requested_address != 0) {
requested_address -= noaccess_prefix; // adjust reque sted address
}
if (special) {
...............................................
}
if (base == NULL) {
if (requested_address != 0) {//classType走这里
base = os::attempt_reserve_memory_at(size, requested_address);
if (failed_to_reserve_as_requested(base, requested_address, size, false)) {
// OS ignored requested address. Try different address.
base = NULL;
}
} else {//nonClassType走这里
base = os::reserve_memory(size, NULL, alignment);
}
if (base == NULL) return;
if ((((size_t)base + noaccess_prefix) & (alignment - 1)) != 0) {
..........................................
}
}
// Done
_base = base;
_size = size;
_alignment = alignment;
_noaccess_prefix = noaccess_prefix;
}
关注两个点_base,_size.
_base : base = os::attempt_reserve_memory_at(size, requested_address); _base = base;
_size : _size = size;
_size = 1073741824B (1G)
_base = 0x800000000-_size = 0x7c0000000
4.2 设置偏移和基地址set_narrow_klass_base_and_shift
void Metaspace::set_narrow_klass_base_and_shift(address metaspace_base, address cds_base) {
address lower_base;
address higher_address;
#if INCLUDE_CDS
if (UseSharedSpaces) {
...........................................................................................
} else
#endif
{
higher_address = metaspace_base + compressed_class_space_size();//0x7c0000000 + 1073741824B = 0x800000000
lower_base = metaspace_base; //0x7c0000000
uint64_t klass_encoding_max = UnscaledClassSpaceMax << LogKlassAlignmentInBytes;
// If compressed class space fits in lower 32G, we don't need a base.
if (higher_address <= (address)klass_encoding_max) { //4.2.1解释
lower_base = 0; // effectively lower base is zero. //30G以下,lower_base =0
}
}
Universe::set_narrow_klass_base(lower_base);
if ((uint64_t)(higher_address - lower_base) <= UnscaledClassSpaceMax) {
Universe::set_narrow_klass_shift(0);//calssType区<=0x10000000不需要压缩
} else {
assert(!UseSharedSpaces, "Cannot shift with UseSharedSpaces");
Universe::set_narrow_klass_shift(LogKlassAlignmentInBytes);
}
}
4.2.1 if (higher_address <= (address)klass_encoding_max)
当Xmx大于30G,小于等于32G时lower_base不会被重置为0。当指针地址大于等于0x8000000000时右移三位是0x100000000已经无法将改指针压缩到4字节了所以需要一个base作为基来扣除。有点抽象,简单举个例子:
假设元空间申请一个klass,地址为kp=0x8100000000,jvm将base记录0x800000000,压缩时先减去base地址。这样地址压缩后就可以小于等4字节了。
step | encode | 代入指针 |
---|---|---|
1 | kp -= base | kp = 0x8100000000 - 0x800000000 = 0x10000000 |
2 | kp >> 3 | kp = 0x10000000 >> 3 = x2000000 压缩后的指针 |
step | decode | 代入指针 |
---|---|---|
1 | kp << 3 | kp = x2000000<< 3 = 0x10000000 |
2 | kp += base | kp = 0x800000000 + 0x10000000 = 0x8100000000 解压后的指针 |
看一下字节码_new首次加载klass时的压缩实现
// 追踪InterpreterRuntime::_new到指针压缩的堆栈
Klass::encode_klass_not_null klass.inline.hpp:55
oopDesc::set_klass oop.inline.hpp:111
CollectedHeap::post_allocation_install_obj_klass collectedHeap.inline.hpp:67
CollectedHeap::post_allocation_setup_common collectedHeap.inline.hpp:44
CollectedHeap::post_allocation_setup_obj collectedHeap.inline.hpp:91
CollectedHeap::obj_allocate collectedHeap.inline.hpp:204
InstanceKlass::allocate_instance instanceKlass.cpp:1124
InterpreterRuntime::_new interpreterRuntime.cpp:174
void TemplateTable::_new() {
...........................................
call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), c_rarg1, c_rarg2);
...........................................
}
//最终压缩实现
inline narrowKlass Klass::encode_klass_not_null(Klass* v) {
int shift = Universe::narrow_klass_shift();
uint64_t pd = (uint64_t)(pointer_delta((void*)v, Universe::narrow_klass_base(), 1));
uint64_t result = pd >> shift;
return (narrowKlass)result;
}
//pointer_delta的实现
inline size_t pointer_delta(const void* left,
const void* right,
size_t element_size) {
return (((uintptr_t) left) - ((uintptr_t) right)) / element_size;
}
4.3 initialize_class_space(metaspace_rs);
初始化了两块区域
static VirtualSpaceList* _class_space_list
和static ChunkManager* _chunk_manager_class
5. nonClassType区static VirtualSpaceList* _space_list
回到global_initialize()函数,
_first_chunk_word_size = InitialBootClassLoaderMetaspaceSize / BytesPerWord;
_first_chunk_word_size = align_word_size_up(_first_chunk_word_size);
size_t word_size = VIRTUALSPACEMULTIPLIER * _first_chunk_word_size; //全局元空间中nonClassType的初始大小
word_size = align_size_up(word_size, Metaspace::reserve_alignment_words());
_space_list = new VirtualSpaceList(word_size);//nonClassType的初始化和申请在“二、元空间的使用 的 1.1.2.2”详说
_chunk_manager_metadata = new ChunkManager(SpecializedChunk, SmallChunk, MediumChunk);
这里申请的就是一块nonClassType区域。结构和_class_space_list是一样的
ChunkManager* Metaspace::_chunk_manager_metadata :
元空间初始化完
二、元空间的使用
1.从全局元空间申请使用
下面的解析Metaspace::MetaspaceType为StandardMetaspaceType;内存区域为nonClass 为例 ,nonClassType和classType的申请流程,判断等基本相同
先看下如何申请元空间的
// 堆栈
Metaspace::initialize metaspace.cpp:3291
Metaspace::Metaspace metaspace.cpp:2939
ClassLoaderData::metaspace_non_null classLoaderData.cpp:433
Metaspace::allocate metaspace.cpp:3498
Array<unsigned char>::operator new array.hpp:325
MetadataFactory::new_writeable_array<unsigned char> metadataFactory.hpp:53
MetadataFactory::new_writeable_array<unsigned char> metadataFactory.hpp:58
1.1 ClassLoaderData::metaspace_non_null()
//根据不同的类加载器,选择不同类型的元空间,设置到单签cld的_metaspace变量
Metaspace* ClassLoaderData::metaspace_non_null() {
if (_metaspace == NULL) {
MutexLockerEx ml(metaspace_lock(), Mutex::_no_safepoint_check_flag);
// Check again if metaspace has been allocated while we were getting this lock.
if (_metaspace != NULL) {
return _metaspace;
}
if (this == the_null_class_loader_data()) {
set_metaspace(new Metaspace(_metaspace_lock, Metaspace::BootMetaspaceType));
} else if (is_anonymous()) {//匿名类,lambda表达式的
set_metaspace(new Metaspace(_metaspace_lock, Metaspace::AnonymousMetaspaceType));
} else if (class_loader()->is_a(SystemDictionary::reflect_DelegatingClassLoader_klass())) {
//JNI膨胀为字节码反射,默认15次阈值会触发
set_metaspace(new Metaspace(_metaspace_lock, Metaspace::ReflectionMetaspaceType));
} else {//标准类加载器
set_metaspace(new Metaspace(_metaspace_lock, Metaspace::StandardMetaspaceType));
}
}
return _metaspace;
}
Metaspace::Metaspace(Mutex* lock, MetaspaceType type) {
initialize(lock, type);
}
void Metaspace::initialize(Mutex* lock, MetaspaceType type) {
_vsm = new SpaceManager(NonClassType, lock); //nonClassType 只是创建了一个内存管理对象
if (_vsm == NULL) {
return;
}
size_t word_size;
size_t class_word_size;
vsm()->get_initial_chunk_sizes(type, &word_size, &class_word_size); // 利用入参回写,根据CLD类型确定nonClasseType和ClasseType申请的大小
if (using_class_space()) {
_class_vsm = new SpaceManager(ClassType, lock);//classType的内存管理对象
if (_class_vsm == NULL) {
return;
}
}
MutexLockerEx cl(SpaceManager::expand_lock(), Mutex::_no_safepoint_check_flag);
// Allocate chunk for metadata objects
Metachunk* new_chunk = get_initialization_chunk(NonClassType,
word_size,
vsm()->medium_chunk_bunch()); //申请nonclasstype内存
assert(!DumpSharedSpaces || new_chunk != NULL, "should have enough space for both chunks");
if (new_chunk != NULL) {
// Add to this manager's list of chunks in use and current_chunk().
vsm()->add_chunk(new_chunk, true);
}
// Allocate chunk for class metadata objects
if (using_class_space()) {
Metachunk* class_chunk = get_initialization_chunk(ClassType,
class_word_size,
class_vsm()->medium_chunk_bunch());//申请classtype内存
if (class_chunk != NULL) {
class_vsm()->add_chunk(class_chunk, true);
}
}
_alloc_record_head = NULL;
_alloc_record_tail = NULL;
}
1.1.1 get_initial_chunk_sizes函数
void SpaceManager::get_initial_chunk_sizes(Metaspace::MetaspaceType type,
size_t* chunk_word_size,
size_t* class_chunk_word_size) {
switch (type) {
case Metaspace::BootMetaspaceType:
*chunk_word_size = Metaspace::first_chunk_word_size();
*class_chunk_word_size = Metaspace::first_class_chunk_word_size();
break;
case Metaspace::ROMetaspaceType:
*chunk_word_size = SharedReadOnlySize / wordSize;
*class_chunk_word_size = ClassSpecializedChunk;
break;
case Metaspace::ReadWriteMetaspaceType:
*chunk_word_size = SharedReadWriteSize / wordSize;
*class_chunk_word_size = ClassSpecializedChunk;
break;
case Metaspace::AnonymousMetaspaceType:
case Metaspace::ReflectionMetaspaceType:
*chunk_word_size = SpecializedChunk;
*class_chunk_word_size = ClassSpecializedChunk;
break;
default: //Metaspace::StandardMetaspaceType
*chunk_word_size = SmallChunk; //512
*class_chunk_word_size = ClassSmallChunk; //256
break;
}
1.1.2get_initialization_chunk函数(NonClassType)
Metachunk* Metaspace::get_initialization_chunk(MetadataType mdtype,
size_t chunk_word_size,
size_t chunk_bunch) {
// Get a chunk from the chunk freelist
Metachunk* chunk = get_chunk_manager(mdtype)->chunk_freelist_allocate(chunk_word_size); //后面扩容时细讲
if (chunk != NULL) {
return chunk;
}
return get_space_list(mdtype)->get_new_chunk(chunk_word_size, chunk_word_size, chunk_bunch);
}
1.1.2.1 get_space_list(mdtype)一个简单三目,获取对应的nonClassType或者classType类型的全局的VirtualSpaceList
1.1.2.2 >get_new_chunk(chunk_word_size, chunk_word_size, chunk_bunch)
Metachunk* VirtualSpaceList::get_new_chunk(size_t word_size,
size_t grow_chunks_by_words,
size_t medium_chunk_bunch) {
-------------------------------------代码片段1-------------------------------------------
// Allocate a chunk out of the current virtual space.
Metachunk* next = current_virtual_space()->get_chunk_vs(grow_chunks_by_words);
if (next != NULL) {
return next;
}
-------------------------------------代码片段2-------------------------------------------
//最小扩容,这是个至少参数,实际这个数只是一个判断依据,而非扩容大小
size_t min_word_size = align_size_up(grow_chunks_by_words, Metaspace::commit_alignment_words());
size_t preferred_word_size = align_size_up(medium_chunk_bunch, Metaspace::commit_alignment_words());//正常扩容大小
if (min_word_size >= preferred_word_size) {
// Can happen when humongous chunks are allocated.
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(grow_chunks_by_words);
assert(next != NULL, "The allocation was expected to succeed after the expansion");
}
return next;
}
代码片段1
Metachunk* VirtualSpaceNode::get_chunk_vs(size_t chunk_word_size) {
assert_lock_strong(SpaceManager::expand_lock());
Metachunk* result = take_from_committed(chunk_word_size);
if (result != NULL) {
inc_container_count();
}
return result;
}
Metachunk* VirtualSpaceNode::take_from_committed(size_t chunk_word_size) {
// Bottom of the new chunk
MetaWord* chunk_limit = top();
if (!is_available(chunk_word_size)) { // 关键看这里
...........................................................
return NULL;
}
inc_top(chunk_word_size);
Metachunk* result = ::new (chunk_limit) Metachunk(chunk_word_size, this);
return result;
}
bool is_available(size_t word_size) {
return word_size <= pointer_delta(end(), _top, sizeof(MetaWord));
}
在is_available函数中end()指的是VirtualSpace的high,sizeof(MetaWord)64位下为8。申请的空间小于等于high减去当前top在除以8,则代表当前元空间可以直接申请使用,不需要扩容。移动top指针:
void inc_top(size_t word_size) { _top += word_size; }假设当前top是0x7f6d252fd800,申请大小512,则申请后的top等于0x7f6d252fe800(C语言的指针加减)
大概情况就是下图这么个情况
假设申请的空间大于high减去当前top在除以8了,元空间就需要从全局空间中申请一块来扩容,在代码段1中next返回则为null,继续走代码段2。
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);
//扩容1------------------------------------------------
bool vs_expanded = expand_node_by(current_virtual_space(),min_words,max_expansion_words);
if (vs_expanded) {
return true;
}
//扩容2------------------------------------------------
retire_current_virtual_space();
size_t grow_vs_words = MAX2((size_t)VirtualSpaceSize, preferred_words);
grow_vs_words = align_size_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;
}
先看下“扩容1”: expand_node_by(current_virtual_space(),min_words,max_expansion_words)
bool VirtualSpaceList::expand_node_by(VirtualSpaceNode* node,
size_t min_words,
size_t preferred_words) {
size_t before = node->committed_words(); //committed_words其实就是已被申请的部分内存
bool result = node->expand_by(min_words, preferred_words);
size_t after = node->committed_words();
assert(after >= before, "Inconsistency");
inc_committed_words(after - before);
return result;
}
bool VirtualSpaceNode::expand_by(size_t min_words, size_t preferred_words) {
size_t min_bytes = min_words * BytesPerWord;
size_t preferred_bytes = preferred_words * BytesPerWord;
//计算未使用的空间总数
size_t uncommitted = virtual_space()->reserved_size() - virtual_space()->actual_committed_size();
if (uncommitted < min_bytes) {//当前申请的不可扩容
return false;
}
size_t commit = MIN2(preferred_bytes, uncommitted);
bool result = virtual_space()->expand_by(commit, false);
assert(result, "Failed to commit memory");
return result;
}
bool result = virtual_space()->expand_by(commit, false);当top和high碰撞时,进行扩容,high和middle_high向高地址移动相应的字节,图示:
在expand_by(commit, false)函数中有这么个判断if (uncommitted_size() < bytes) return false;意思是VirtualSpaceList链表中,当前使用的VirtualSpaceNode的空间已经无法分配足够的空间,返回false到“扩容2“继续执行, “扩容2“先执行retire_current_virtual_space();函数。
void VirtualSpaceList::retire_current_virtual_space() {
assert_lock_strong(SpaceManager::expand_lock());
VirtualSpaceNode* vsn = current_virtual_space();
ChunkManager* cm = is_class() ? Metaspace::chunk_manager_class() :
Metaspace::chunk_manager_metadata();
vsn->retire(cm);
}
void VirtualSpaceNode::retire(ChunkManager* chunk_manager) {
for (int i = (int)MediumIndex; i >= (int)ZeroIndex; --i) {
ChunkIndex index = (ChunkIndex)i;
size_t chunk_size = chunk_manager->free_chunks(index)->size();
while (free_words_in_vs() >= chunk_size) {
DEBUG_ONLY(verify_container_count();)
Metachunk* chunk = get_chunk_vs(chunk_size);
assert(chunk != NULL, "allocation should have been successful");
chunk_manager->return_chunks(index, chunk);
chunk_manager->inc_free_chunks_total(chunk_size);
DEBUG_ONLY(verify_container_count();)
}
}
assert(free_words_in_vs() == 0, "should be empty now");
}
}
将当前的VirtualSpaceNode空间中多余的空间(top到high的距离),根据下标ZeroIndex=0,SpecializedIndex=0,SmallIndex=1,MediumIndex=2对应大小128,128,512,8912依次对比,选择比(top-high)小的链表切割后存入ChunkManager。解释的有点抽象。简单举个例子:申请的空间是4096B,VirtualSpaceNode中未使用的top到high剩余是2048B根据if (uncommitted < min_bytes)的判断,不可申请,high也不可移动,就会走retire_current_virtual_space函数。已知VirtualSpaceNode中有剩余内存2048B,这些内存做8字节分割:pointer_delta(end(), top(), sizeof(MetaWord)) = 256。根据4个下标对应的size,通过代码while (free_words_in_vs() >= chunk_size)来判断。
MediumIndex = 2,chunk_size= 8912大于256不成立;
SmallIndex=1,chunk_size= 512大于256不成立;
ZeroIndex、SpecializedIndex=0,chunk_size= 128小于256成立,分两次放入下标为0的free_chunks[0]链表中(retire函数是个嵌套循环)。
//链表的代码
void FreeList<Chunk>::return_chunk_at_head(Chunk* chunk, bool record_return) {
Chunk* oldHead = head(); //原有的链表头
chunk->link_after(oldHead);//当前链表的next指向原来的链表头
link_head(chunk);//设置当前链表的_head属性的指针
if (oldHead == NULL) { // only chunk in list
link_tail(chunk);
}
increment_count(); // of # of chunks in list
}
void link_tail(Chunk_t* v) {
set_tail(v); // 设置链表tail属性的指针
if (v != NULL) {
v->clear_next();
}
}
第一次进入head、tail相同 | 第二次进入head变化 |
---|---|
在回看1.1.2中Metachunk* chunk = get_chunk_manager(mdtype)->chunk_freelist_allocate(chunk_word_size);这段
get_chunk_manager(mdtype)函数根据类型返回对应的ChunkManager。在上面元空间初始化中,nonClassType和classType的ChunkManager结构是一样的主要是一个数组里的三个列表以及一个_humongous_dictionary(暂时未找到使用场景)
ChunkList _free_chunks[NumberOfFreeLists];
ChunkManager(size_t specialized_size, size_t small_size, size_t medium_size)
: _free_chunks_total(0), _free_chunks_count(0) {
_free_chunks[SpecializedIndex].set_size(specialized_size);
_free_chunks[SmallIndex].set_size(small_size);
_free_chunks[MediumIndex].set_size(medium_size);
}
调用ChunkManager的chunk_freelist_allocate(chunk_word_size)函数根据get_initial_chunk_sizes函数中case对应的size来选择不同的Chunk
Metachunk* ChunkManager::chunk_freelist_allocate(size_t word_size) {
...........................
Metachunk* chunk = free_chunks_get(word_size);
if (chunk == NULL) {
return NULL;
}
..........................
return chunk;
}
Metachunk* ChunkManager::free_chunks_get(size_t word_size) {
Metachunk* chunk = NULL;
if (list_index(word_size) != HumongousIndex) { //这里,根据size获得对应的下标,word_size是根据类加载器来定义
ChunkList* free_list = find_free_chunks_list(word_size);//根据size获得下标,在根据下标获得ChunkList
assert(free_list != NULL, "Sanity check");
chunk = free_list->head();//第一个也是最后加入ChunkList的Metachunk
if (chunk == NULL) {
return NULL;
}
// Remove the chunk as the head of the list.
free_list->remove_chunk(chunk);//将被选中的从ChunkList中移除,因为要被commit了
.................................................
} else {
chunk = humongous_dictionary()->get_chunk(
word_size,
FreeBlockDictionary<Metachunk>::atLeast);
if (chunk == NULL) {
return NULL;
}
}
..................................................
// Chunk is being removed from the chunks free list.
dec_free_chunks_total(chunk->word_size());
// 清理next和prev节点,因为这个chunk对应的内存将被使用,chunk也就不存在了
chunk->set_next(NULL);
chunk->set_prev(NULL);
#ifdef ASSERT
// Chunk is no longer on any freelist. Setting to false make container_count_slow()
// work.
chunk->set_is_tagged_free(false);
#endif
chunk->container()->inc_container_count();
slow_locked_verify();
return chunk;
}
ChunkList* ChunkManager::find_free_chunks_list(size_t word_size) {
ChunkIndex index = list_index(word_size);
assert(index < HumongousIndex, "No humongous list");
return free_chunks(index);
}
回到 expand_by(size_t min_words, size_t preferred_words)继续往下要新创建一个VirtualSpaceNode。
size_t grow_vs_words = MAX2((size_t)VirtualSpaceSize, preferred_words);
grow_vs_words = align_size_up(grow_vs_words, Metaspace::reserve_alignment_words());
grow_vs_words = 256*1024 = 262144这是要创建的大小
create_new_virtual_space(grow_vs_words)函数中创建这里正好补充下元空间初始化global_initialize()函数中_space_list = new VirtualSpaceList(word_size);这一段。
//---------------------------------------VirtualSpaceList-------------------------------------------------
VirtualSpaceList::VirtualSpaceList(size_t word_size) :
_is_class(false),
_virtual_space_list(NULL),
_current_virtual_space(NULL),
_reserved_words(0),
_committed_words(0),
_virtual_space_count(0) {
MutexLockerEx cl(SpaceManager::expand_lock(),
Mutex::_no_safepoint_check_flag);
create_new_virtual_space(word_size); //同样调用这个函数创建第一个VirtualSpaceNode,word_size比后面创建的要大
}
bool VirtualSpaceList::create_new_virtual_space(size_t vs_word_size) {
assert_lock_strong(SpaceManager::expand_lock());
if (is_class()) {
return false;
}
if (vs_word_size == 0) {
assert(false, "vs_word_size should always be at least _reserve_alignment large.");
return false;
}
// Reserve the space
size_t vs_byte_size = vs_word_size * BytesPerWord;//262144 * 8 = 2097152
assert_is_size_aligned(vs_byte_size, Metaspace::reserve_alignment());
// Allocate the meta virtual space and initialize it.
VirtualSpaceNode* new_entry = new VirtualSpaceNode(vs_byte_size);
if (!new_entry->initialize()) {
delete new_entry;
return false;
} else {
OrderAccess::storestore();
link_vs(new_entry);
return true;
}
}
VirtualSpaceNode::VirtualSpaceNode(size_t bytes) : _top(NULL), _next(NULL), _rs(), _container_count(0) {
assert_is_size_aligned(bytes, Metaspace::reserve_alignment());
#if INCLUDE_CDS
if (DumpSharedSpaces) {
.................................................................
} else
#endif
{
bool large_pages = should_commit_large_pages_when_reserving(bytes);
_rs = ReservedSpace(bytes, Metaspace::reserve_alignment(), large_pages);
}
if (_rs.is_reserved(){
................................................................
}
}
//这部分回看“一、元空间初始化的4.1”
ReservedSpace::ReservedSpace(size_t size, size_t alignment,
bool large,
char* requested_address,
const size_t noaccess_prefix) {
initialize(size+noaccess_prefix, alignment, large, requested_address,
noaccess_prefix, false);
}
new VirtualSpaceNode执行完后new_entry->initialize()
bool VirtualSpaceNode::initialize() {
if (!_rs.is_reserved()) {
return false;
}
size_t pre_committed_size = _rs.special() ? _rs.size() : 0;
bool result = virtual_space()->initialize_with_granularity(_rs, pre_committed_size,
Metaspace::commit_alignment());
if (result) {
set_top((MetaWord*)virtual_space()->low());
set_reserved(MemRegion((HeapWord*)_rs.base(),
(HeapWord*)(_rs.base() + _rs.size())));
}
return result;
}
initialize_with_granularity函数初始化VirtualSpace中的真实内存的指针,VirtualSpace就是一个容器,存着已申请内存锁对应的base、top、各个boundary的地址,这是这些参数是指针碰撞的基础支持详细指针看“一、元空间初始化的5的图”。
set_top((MetaWord*)virtual_space()->low());
初始化top指针,initialize_with_granularity函数初始化时low就是base,所以virtual_space()->low()得到的也是base的地址
set_reserved(MemRegion((HeapWord*)_rs.base(), (HeapWord*)(_rs.base() + _rs.size())));
这个嘛设置一个MemRegion。同样看“一、元空间初始化的5的图”。
初始化后将当前node链接到VirtualSpaceList链表 link_vs(new_entry)函数;
void VirtualSpaceList::link_vs(VirtualSpaceNode* new_entry) {
if (virtual_space_list() == NULL) { //全局初始化时走这里,设置第一个
set_virtual_space_list(new_entry);
} else {
current_virtual_space()->set_next(new_entry);//第n次创建node走这里,将原来队列中的设置到当前的next中头插法
}
set_current_virtual_space(new_entry);//设置当前使用的的node,通过这个node中的内存空间指针来使用元空间
inc_reserved_words(new_entry->reserved_words());
inc_committed_words(new_entry->committed_words());//提交当前node的内存空间指向,表示该内存段已被申请
inc_virtual_space_count();
#ifdef ASSERT
new_entry->mangle();
#endif
if (TraceMetavirtualspaceAllocation && Verbose) {
VirtualSpaceNode* vsl = current_virtual_space();
vsl->print_on(gclog_or_tty);
}
}
链接前 | 当前new出来的node | 链接后,当前的指针是行地址,next是原来的地址 |
---|---|---|
最后“扩容2”中还有个expand_node_by回看下这节的 “扩容1”,是一样的。 |
小总结一下
1 元空间全局初始化 前半部分:ClassType区域在(“一、元空间初始化”的4.3小节图示)和全局NonClassType区域(在“一、元空间初始化”的5小节图示)。此时所有空间都是uncommitted并初始化ClassType和NonClassType各一个VirtualSpaceList链表管理。接着后半部分初始化一个VirtualSpaceNode,设置_top,设置_virtual_space中的_low、middle、high和各种边界,将node中的内存全局commited表示已被申请,链接到VirtualSpaceList中。
2 对ClassType或NonClassType对应的VirtualSpaceNode申请对应cld中所需的内存,判断当前node的high指针减去top是否够申请使用,够用改这部分内存为node中的commited,移动node的top,内存用Metachunk管理。不够则扩容
3 扩容时申请ClassType或NonClassType的内存用VirtualSpaceNode管理,改这部分内存为全局的commited,这部分内存在VirtualSpaceNode中是uncommitted的(和初始的后半部分基本相同)并将当前的node和VirtualSpaceList链接。在重复第二部。
4 元空间内存有三种状态:
1 全局的uncommitted
2 被VirtualSpaceNode管理的全局commited,node中uncommitted
3 被Metachunk管理后在node中的commited
三、元空间填充
在“ 二、元空间的使用“→"1.从全局元空间申请及扩容“→“1.1 ClassLoaderData::metaspace_non_null()”的代码片段的第三个函数中无论是classType还是nonClassType都会创建一个new SpaceManager(NonClassType, lock),当一个空间申请完后需要将这个空间通过函数add_chunk(new_chunk, true)填充一些SpaceManager的属性
void SpaceManager::add_chunk(Metachunk* new_chunk, bool make_current) {
new_chunk->reset_empty();//初始化Metachunk的_top,_next,_pre
ChunkIndex index = ChunkManager::list_index(new_chunk->word_size());//解释过
if (index != HumongousIndex) {
retire_current_chunk();//GC相关
set_current_chunk(new_chunk);//将当前Metachunk设置到SpaceManager的全局变量Metachunk* _current_chunk;
new_chunk->set_next(chunks_in_use(index));//这两个看下面布局图
set_chunks_in_use(index, new_chunk);
} else {//humongous先不管了
............................................................................
}
........................................................
}
Metachunk* _current_chunk;Metachunk上面说过了,这里就直接看一个普通java类的klass信息如填充元空间。
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray<Handle>* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS) {
...................................................................................
_klass = InstanceKlass::allocate_instance_klass(loader_data,
vtable_size,
itable_size,
info.static_field_size,
total_oop_map_size2,
rt,
access_flags,
name,
super_klass(),
!host_klass.is_null(),
CHECK_(nullHandle));
....................................................................................
}
InstanceKlass* InstanceKlass::allocate_instance_klass(
ClassLoaderData* loader_data,
int vtable_len,
int itable_len,
int static_field_size,
int nonstatic_oop_map_size,
ReferenceType rt,
AccessFlags access_flags,
Symbol* name,
Klass* super_klass,
bool is_anonymous,
TRAPS) {
int size = InstanceKlass::size(vtable_len, itable_len, nonstatic_oop_map_size,
access_flags.is_interface(), is_anonymous); //本文不涉及,这个size就是要申请的klass大小
// Allocation
InstanceKlass* ik;
if (rt == REF_NONE) {
if (name == vmSymbols::java_lang_Class()) {
....................................................
} else if (name == vmSymbols::java_lang_ClassLoader() ||
(SystemDictionary::ClassLoader_klass_loaded() &&
super_klass != NULL &&
super_klass->is_subtype_of(SystemDictionary::ClassLoader_klass()))) {
.....................................................
} else {
// normal class
ik = new (loader_data, size, THREAD) InstanceKlass(
vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
access_flags, is_anonymous);
}
} else {
.....................................................
}
.....................................................
loader_data->add_class(ik);
Atomic::inc(&_total_instanceKlass_count);
return ik;
}
操作符重载new (loader_data, size, THREAD),
void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
return Metaspace::allocate(loader_data, word_size, /*read_only*/false,
MetaspaceObj::ClassType, CHECK_NULL);
}
MetaWord* Metaspace::allocate(ClassLoaderData* loader_data, size_t word_size,
bool read_only, MetaspaceObj::Type type, TRAPS) {
if (HAS_PENDING_EXCEPTION) {
.....................................................
}
if (DumpSharedSpaces) {
.....................................................
return result;
}
MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType;
// metaspace_non_null能走到这表示non_null了,
MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);
if (result == NULL) {
tracer()->report_metaspace_allocation_failure(loader_data, word_size, type, mdtype);
// Allocation failed.
if (is_init_completed()) {
// Only start a GC if the bootstrapping has completed.
// Try to clean out some memory and retry.
result = Universe::heap()->collector_policy()->satisfy_failed_metadata_allocation(
loader_data, word_size, mdtype);
}
}
if (result == NULL) {
report_metadata_oome(loader_data, word_size, type, mdtype, CHECK_NULL);
}
// Zero initialize.
Copy::fill_to_aligned_words((HeapWord*)result, word_size, 0);
return result;
}
MetaWord* Metaspace::allocate(size_t word_size, MetadataType mdtype) {
if (is_class_space_allocation(mdtype)) {
return class_vsm()->allocate(word_size);//klass信息肯定走这里
} else {
return 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_raw_word_size(word_size);
BlockFreelist* fl = block_freelists();
MetaWord* p = NULL;
if (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;
}
MetaWord* SpaceManager::allocate_work(size_t word_size) {
...............................................
if (current_chunk() != NULL) {
result = current_chunk()->allocate(word_size);
}
...............................................
return result;
}
MetaWord* Metachunk::allocate(size_t word_size) {
MetaWord* result = NULL;
// If available, bump the pointer to allocate.
if (free_word_size() >= word_size) {
result = _top;
_top = _top + word_size;
}
return result;
}
最终调用Metachunk::allocate(size_t word_size)函数中将当前的top返回回去做为klass信息所需存储的内存起始地址,top在后移这个klass信息所需内存的步长(MetaWord* 为单位,也就是word_size)作为下一次申请使用空间的首地址。最后向内存写入klass信息