Hotspot源码解析-第十八章-元空间的创建与分配

元空间就是从C堆中划出来的一片完整的区域,为了提升元数据的内存分配效率,又把元空间按若干个chunk内存块管理起来,其中chunk块又分为已使用和空间两种类型,并分别用VirtualSpaceList和ChunkManager来管理,chunk内存块之间以链表的形式关联起来,同时为了满足不同元数据占用内存大小的内存分配,chunk内存块也是有多种不同大小的chunk,如SpecializedChunk, SmallChunk, MediumChunk,分别表示128B、512B、8K大小。本章要做的工作就是在实际分配内存存放元数据前的一切准备工作。

18.1 metaspace.cpp

18.1.1 Metaspace::global_initialize

void Metaspace::global_initialize() {
  // 这一步就是给_capacity_until_GC参数赋值为最大元空间大小:_capacity_until_GC = MaxMetaspaceSize;表示当空间到_capacity_until_GC的值时,就会触发GC操作
  MetaspaceGC::initialize();

  // 初始化共享空间的对齐大小,实际上vm_allocation_granularity()里面就是去拿page size(页大小4*k)
  int max_alignment = os::vm_allocation_granularity();
  size_t cds_total = 0;
  // 设置空间对齐值
  MetaspaceShared::set_max_alignment(max_alignment);
  /* DumpSharedSpaces 表示共享空间转储到文件,默认是不开启的,为了减少HotSpot源码的复杂性,这个也按默认false处理,这条线也不走。
  JVM可以在多个Java进程之间共享公共类元数据,以减少内存使用并缩短启动时间。该共享类数据存储在共享空间。实际生产中也不会这么做,所以可以忽略。如果想开启,可以按如下命令执行:
  java -XX:+DumpSharedSpaces -XX:SharedArchiveFile=yourSharedSpaces.jsa -jar yourApplication.jar
  */
  if (DumpSharedSpaces) {
#if INCLUDE_CDS
    MetaspaceShared::estimate_regions_size();

    SharedReadOnlySize  = align_size_up(SharedReadOnlySize,  max_alignment);
    SharedReadWriteSize = align_size_up(SharedReadWriteSize, max_alignment);
    SharedMiscDataSize  = align_size_up(SharedMiscDataSize,  max_alignment);
    SharedMiscCodeSize  = align_size_up(SharedMiscCodeSize,  max_alignment);

    // the min_misc_code_size estimate is based on MetaspaceShared::generate_vtable_methods()
    uintx min_misc_code_size = align_size_up(
      (MetaspaceShared::num_virtuals * MetaspaceShared::vtbl_list_size) *
        (sizeof(void*) + MetaspaceShared::vtbl_method_size) + MetaspaceShared::vtbl_common_code_size,
          max_alignment);

    if (SharedMiscCodeSize < min_misc_code_size) {
      report_out_of_shared_space(SharedMiscCode);
    }

    // Initialize with the sum of the shared space sizes.  The read-only
    // and read write metaspace chunks will be allocated out of this and the
    // remainder is the misc code and data chunks.
    cds_total = FileMapInfo::shared_spaces_size();
    cds_total = align_size_up(cds_total, _reserve_alignment);
    _space_list = new VirtualSpaceList(cds_total/wordSize);
    _chunk_manager_metadata = new ChunkManager(SpecializedChunk, SmallChunk, MediumChunk);

    if (!_space_list->initialization_succeeded()) {
      vm_exit_during_initialization("Unable to dump shared archive.", NULL);
    }

#ifdef _LP64
    if (cds_total + compressed_class_space_size() > UnscaledClassSpaceMax) {
      vm_exit_during_initialization("Unable to dump shared archive.",
          err_msg("Size of archive (" SIZE_FORMAT ") + compressed class space ("
                  SIZE_FORMAT ") == total (" SIZE_FORMAT ") is larger than compressed "
                  "klass limit: " SIZE_FORMAT, cds_total, compressed_class_space_size(),
                  cds_total + compressed_class_space_size(), UnscaledClassSpaceMax));
    }

    // Set the compressed klass pointer base so that decoding of these pointers works
    // properly when creating the shared archive.
    assert(UseCompressedOops && UseCompressedClassPointers,
      "UseCompressedOops and UseCompressedClassPointers must be set");
    Universe::set_narrow_klass_base((address)_space_list->current_virtual_space()->bottom());
    if (TraceMetavirtualspaceAllocation && Verbose) {
      gclog_or_tty->print_cr("Setting_narrow_klass_base to Address: " PTR_FORMAT,
                             _space_list->current_virtual_space()->bottom());
    }

    Universe::set_narrow_klass_shift(0);
#endif // _LP64
#endif // INCLUDE_CDS
  } else {
#if INCLUDE_CDS
    /*
	 UseSharedSpaces:启用共享存档,这个空间主要是针对热点数据的存储,比如包含元数据、字节码和其他相关信息的共享存档文件。开启命令如下:
	   java -Xshare:dump -XX:SharedArchiveFile=yourSharedArchive.jsa -jar yourApplication.jar
	   这块默认是开启的,该文档用于存放CDS数据(类共享数据),该数据用于缩短Java应用程序的启动时间并减少内存占用。CDS的想法是创建一个包含预先计算的数据结构、类元数据和字节码的共享存档文件。该存档可以内存映射mmap到多个Java进程地址空间,允许它们共享公共类和资源(这样省去了JVM启动时对公共类的加载、分配内存等时间和内存空间)。
    */
    address cds_address = NULL;
    if (UseSharedSpaces) {
      // 找到那个存档文件,并封装成FileMapInfo
      FileMapInfo* mapinfo = new FileMapInfo();
      // map_shared_spaces()函数,将存档文件映射到当前Java进程的内存地址空间
      if (mapinfo->initialize() && MetaspaceShared::map_shared_spaces(mapinfo)) {
        cds_total = FileMapInfo::shared_spaces_size();
        cds_address = (address)mapinfo->region_base(0);
      } else {
        assert(!mapinfo->is_open() && !UseSharedSpaces,
               "archive file not closed or shared spaces not disabled.");
      }
    }
#endif // INCLUDE_CDS
#ifdef _LP64  // 64位机器不在考虑范围内
    // If UseCompressedClassPointers is set then allocate the metaspace area
    // above the heap and above the CDS area (if it exists).
    if (using_class_space()) {
      if (UseSharedSpaces) {
#if INCLUDE_CDS
        char* cds_end = (char*)(cds_address + cds_total);
        cds_end = (char *)align_ptr_up(cds_end, _reserve_alignment);
        allocate_metaspace_compressed_klass_ptrs(cds_end, cds_address);
#endif
      } 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());

    // 创建VirtualSpaceList来管理已使用的chunk,细节看`章节18.1.2`
    _space_list = new VirtualSpaceList(word_size);
    // 创建ChunkManager来管理空闲的chunk,这一步的操作看`章节18.1.3`
    _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();
}

18.1.2 VirtualSpaceList::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);  // 实际做事的函数
}

bool VirtualSpaceList::create_new_virtual_space(size_t vs_word_size) {
  assert_lock_strong(SpaceManager::expand_lock());
  // 上面初始化时,已经设置_is_class = false,所以这条逻辑不会走
  if (is_class()) {
    assert(false, "We currently don't support more than one VirtualSpace for"
                  " the compressed class space. The initialization of the"
                  " CCS uses another code path and should not hit this path.");
    return false;
  }
  // 大小不能为0
  if (vs_word_size == 0) {
    assert(false, "vs_word_size should always be at least _reserve_alignment large.");
    return false;
  }

  // 求得对齐后,要分配的字节大小
  size_t vs_byte_size = vs_word_size * BytesPerWord;
  assert_is_size_aligned(vs_byte_size, Metaspace::reserve_alignment());

  // 创建并分配VirtualSpaceNode对象,看下面VirtualSpaceNode::VirtualSpaceNode函数
  VirtualSpaceNode* new_entry = new VirtualSpaceNode(vs_byte_size);
  //   创建完后,要对字段进行初始化,看下面VirtualSpaceNode::initialize函数
  if (!new_entry->initialize()) {
    delete new_entry;
    return false;
  } else {
    assert(new_entry->reserved_words() == vs_word_size,
        "Reserved memory size differs from requested memory size");
    // ensure lock-free iteration sees fully initialized node
    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
  // This allocates memory with mmap.  For DumpSharedspaces, try to reserve
  // configurable address, generally at the top of the Java heap so other
  // memory addresses don't conflict.
  if (DumpSharedSpaces) {
    bool large_pages = false; // No large pages when dumping the CDS archive.
    char* shared_base = (char*)align_ptr_up((char*)SharedBaseAddress, Metaspace::reserve_alignment());

    _rs = ReservedSpace(bytes, Metaspace::reserve_alignment(), large_pages, shared_base, 0);
    if (_rs.is_reserved()) {
      assert(shared_base == 0 || _rs.base() == shared_base, "should match");
    } else {
      // Get a mmap region anywhere if the SharedBaseAddress fails.
      _rs = ReservedSpace(bytes, Metaspace::reserve_alignment(), large_pages);
    }
    MetaspaceShared::set_shared_rs(&_rs);
  } else
#endif
  {
    bool large_pages = should_commit_large_pages_when_reserving(bytes);
    // 直接看这一行,这里创建一个ReservedSpace对象句柄,同时创建指定大小的内存区域,里面用的是mmap映射方式,细节在前面的章节`章节17.4.3.2`和`章节17.4.3.3`中有描述,可以返回去看,总之,这里才是真正给元空间mmap映射了一块内存区域
    _rs = ReservedSpace(bytes, Metaspace::reserve_alignment(), large_pages);
  }

  if (_rs.is_reserved()) {
    assert(_rs.base() != NULL, "Catch if we get a NULL address");
    assert(_rs.size() != 0, "Catch if we get a 0 size");
    assert_is_ptr_aligned(_rs.base(), Metaspace::reserve_alignment());
    assert_is_size_aligned(_rs.size(), Metaspace::reserve_alignment());

    MemTracker::record_virtual_memory_type((address)_rs.base(), mtClass);
  }
}

bool VirtualSpaceNode::initialize() {
  // 由上一步VirtualSpaceNode::VirtualSpaceNode函数映射的内存区域用ReservedSpace对象来持有,命名为_rs,该对象创建的内存is_reserved()一定是返回true,否则return false退出函数
  if (!_rs.is_reserved()) {
    return false;
  }

  // 断言验证_rs的base和size值都是对齐后的值
  assert_is_ptr_aligned(_rs.base(), Metaspace::commit_alignment());
  assert_is_size_aligned(_rs.size(), Metaspace::commit_alignment());

  // 由前面创建_rs得知_rs.special()返回的是false,所以这里pre_committed_size就是0
  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) {
    assert(virtual_space()->committed_size() == virtual_space()->actual_committed_size(),
        "Checking that the pre-committed memory was registered by the VirtualSpace");
    // 设置top值,也就是内存空间的限制界线值
    set_top((MetaWord*)virtual_space()->low());
    // 设置reserved的值,其实就是上面_rs的值再通过MemRegion来包装
    set_reserved(MemRegion((HeapWord*)_rs.base(),
                 (HeapWord*)(_rs.base() + _rs.size())));

    assert(reserved()->start() == (HeapWord*) _rs.base(),
      err_msg("Reserved start was not set properly " PTR_FORMAT
        " != " PTR_FORMAT, reserved()->start(), _rs.base()));
    assert(reserved()->word_size() == _rs.size() / BytesPerWord,
      err_msg("Reserved size was not set properly " SIZE_FORMAT
        " != " SIZE_FORMAT, reserved()->word_size(),
        _rs.size() / BytesPerWord));
  }
  // 返回结果
  return result;
}

18.1.3 ChunkManager构造函数

ChunkManager(size_t specialized_size, size_t small_size, size_t medium_size)
      : _free_chunks_total(0), _free_chunks_count(0) {
    // 上面提到过chunk块大小有多种,各不一样,这里分别对元空间涉及的3种chunk块设置成_free_chunks数组中的元素,_free_chunks数组指向的是FreeList类型的链表,用于链接所包含的所有chunk块,这里的size是指FreeList链表中每个chunk的大小,千万不要误认为是FreeList链表的总大小
    _free_chunks[SpecializedIndex].set_size(specialized_size);
    _free_chunks[SmallIndex].set_size(small_size);
    _free_chunks[MediumIndex].set_size(medium_size);
  }

你可能感兴趣的:(Java虚拟机,java)