jvm元空间和指针压缩

****本文不涉及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字节了。

stepencode代入指针
1kp -= basekp = 0x8100000000 - 0x800000000 = 0x10000000
2kp >> 3kp = 0x10000000 >> 3 = x2000000 压缩后的指针
stepdecode代入指针
1kp << 3kp = x2000000<< 3 = 0x10000000
2kp += basekp = 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变化
电脑$1600

在回看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信息

java类的全局变量元空间布局

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值