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

这篇具有很好参考价值的文章主要介绍了Hotspot源码解析-第十八章-元空间的创建与分配。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

到了这里,关于Hotspot源码解析-第十八章-元空间的创建与分配的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Hotspot源码解析-第二十章-字典表创建和基础类预加载(四)

    20.5.1 systemDictionary.cpp/hpp 20.5.1.1 SystemDictionary::initialize 20.5.1.2 initialize_preloaded_classes 这一部分也用到了很多宏定义,咱们先把宏展开后再来讲解 要使用的宏已经展开了,下面接着讲正题 SystemDictionary::initialize_wk_klasses_until SystemDictionary::initialize_wk_klass

    2024年01月19日
    浏览(33)
  • 《TCP IP网络编程》第十八章

    线程背景:         第 10 章介绍了多进程服务端的实现方法。多进程模型与 select 和 epoll 相比的确有自身的优点,但同时也有问题。如前所述, 创建(复制)进程的工作本身会给操作系统带来相当沉重的负担。而且,每个进程都具有独立的内存空间,所以进程间通信的实

    2024年02月12日
    浏览(49)
  • 【新版系统架构】第十八章-安全架构设计理论与实践

    信息系统安全设计重点考虑:系统安全保障体系,信息安全体系架构 系统安全保障体系: 安全区域策略的确定,根据安全区域的划分,主管部门应制定针对性的安全策略 统一配置和管理防病毒系统,主管部门应当建立整体防御策略,以实现统一的配置和管理 网络安全管理,

    2024年02月13日
    浏览(45)
  • 华为HCIE课堂笔记第十八章 SR技术

    SR可以通过控制器实现集中算路,并根据业务的不同下发不同的路径给到头端设备,头端设备将路径标签通过标签栈的形式压入到报文中,沿途的设备不需要维护路径信息,仅按照标签栈中的栈顶进行报文转发即可。 控制平面:扩展后的IGP协议,通过IGP分发标签 转发平面:基

    2024年02月19日
    浏览(39)
  • Docker第十八章 : 构建您的第一个Java镜像

    第十八章 : 构建您的第一个Java镜像 本章知识点: 介绍构建java镜像的基本步骤,以及如何通过阿里云效和阿里云容器镜像服务,构建您的第一个Java镜像。 导读 Java入门指南教您如何使用Docker创建容器化的Spring Boot应用程序。在本模块中,您将学习如何: 使用Maven克隆并运行

    2024年02月22日
    浏览(52)
  • 第十八章_Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透

    缓存预热 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。 可以通过@PostConstruct初始化白名单数据 缓存雪崩 发生  redis主机挂了,Redis 全盘崩溃

    2024年02月07日
    浏览(44)
  • 斯坦福 Stats60:21 世纪的统计学:第十五章到第十八章

    原文: statsthinking21.github.io/statsthinking21-core-site/comparing-means.html 译者:飞龙 协议:CC BY-NC-SA 4.0 我们已经遇到了许多情况,我们想要询问样本均值的问题。在本章中,我们将更深入地探讨我们可以比较不同组均值的各种方法。 我们可能想要询问均值是否具有特定值的最简单的

    2024年01月16日
    浏览(48)
  • C++ Primer第五版_第十八章习题答案(11~20)

    练习18.11 为什么 what 函数不应该抛出异常? what中如果抛出异常,需要try catch捕获,再调用what,一直循环,直达内存耗尽。 练习18.12 将你为之前各章练习编写的程序放置在各自的命名空间中。也就是说,命名空间chapter15包含Query程序的代码,命名空间chapter10包含TextQuery的代码

    2024年02月06日
    浏览(43)
  • 【Rust】Rust学习 第十八章模式用来匹配值的结构

    模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。结合使用模式和  match  表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成: 字面值 解构的数组、枚举、结构体或者元组 变量 通配符 占位符 这些部分描

    2024年02月11日
    浏览(43)
  • Go学习第十八章——Gin日志与第三方工具Logrus

    1.1 快速入门 在使用Gin框架的过程中,日志是一个重要的组成部分,它可以记录框架和应用程序的运行情况,帮助开发者排查问题和监控应用程序的性能。Gin框架提供了方便的方法来设置和使用日志。 默认日志 Gin框架默认使用的是标准库的log包,将日志输出到控制台。可以通

    2024年02月07日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包