OpenJDK ZGC 源码分析(三)内存管理

1. 简介

ZGC与传统GC不同,标记阶段标记的是指针(colored pointer),而非传统GC算法中的标记对象。ZGC借助内存映射,将多个地址映射到同一个内存文件描述符上,使得ZGC回收周期各阶段能够使用不同的地址访问同一对象。

本文将详细介绍ZGC的内存管理。

2. 代码分析

2.1 指针结构

目前ZGC仅支持Linux/x64,指针是64位的,ZGC重新定义了指针结构。
zGlobals_linux_x86.hpp

// Address Space & Pointer Layout
// ------------------------------
//
//  +--------------------------------+ 0x00007FFFFFFFFFFF (127TB)
//  .                                .
//  .                                .
//  .                                .
//  +--------------------------------+ 0x0000140000000000 (20TB)
//  |         Remapped View          |
//  +--------------------------------+ 0x0000100000000000 (16TB)
//  |     (Reserved, but unused)     |
//  +--------------------------------+ 0x00000c0000000000 (12TB)
//  |         Marked1 View           |
//  +--------------------------------+ 0x0000080000000000 (8TB)
//  |         Marked0 View           |
//  +--------------------------------+ 0x0000040000000000 (4TB)
//  .                                .
//  +--------------------------------+ 0x0000000000000000
//
//
//   6                 4 4 4  4 4                                             0
//   3                 7 6 5  2 1                                             0
//  +-------------------+-+----+-----------------------------------------------+
//  |00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111|
//  +-------------------+-+----+-----------------------------------------------+
//  |                   | |    |
//  |                   | |    * 41-0 Object Offset (42-bits, 4TB address space)
//  |                   | |
//  |                   | * 45-42 Metadata Bits (4-bits)  0001 = Marked0      (Address view 4-8TB)
//  |                   |                                 0010 = Marked1      (Address view 8-12TB)
//  |                   |                                 0100 = Remapped     (Address view 16-20TB)
//  |                   |                                 1000 = Finalizable  (Address view N/A)
//  |                   |
//  |                   * 46-46 Unused (1-bit, always zero)
//  |
//  * 63-47 Fixed (17-bits, always zero)
//

const size_t    ZPlatformAddressOffsetBits     = 42; // 4TB
  • ZGC使用41-0位记录对象地址,42位地址为应用程序提供了理论4TB的堆空间
  • 45-42位为metadata比特位, 对应于如下状态: finalizable,remapped,marked1和marked0
  • 46位为保留位,固定为0
  • 63-47位固定为0

2.2 内存文件映射

zBackingFile_linux_x86.cpp

// Java heap filename
#define ZFILENAME_HEAP                   "java_heap"

ZBackingFile::ZBackingFile() :
    _fd(-1),
    _filesystem(0),
    _available(0),
    _initialized(false) {

  // Create backing file
  _fd = create_fd(ZFILENAME_HEAP);
  if (_fd == -1) {
    return;
  }
}
  • 内存映射之前需要创建文件描述符,名称为java_heap
int ZBackingFile::create_fd(const char* name) const {
  if (ZPath == NULL) {
    // If the path is not explicitly specified, then we first try to create a memfd file
    // instead of looking for a tmpfd/hugetlbfs mount point. Note that memfd_create() might
    // not be supported at all (requires kernel >= 3.17), or it might not support large
    // pages (requires kernel >= 4.14). If memfd_create() fails, then we try to create a
    // file on an accessible tmpfs or hugetlbfs mount point.
    const int fd = create_mem_fd(name);
    if (fd != -1) {
      return fd;
    }

    log_debug(gc, init)("Falling back to searching for an accessible mount point");
  }

  return create_file_fd(name);
}

int ZBackingFile::create_mem_fd(const char* name) const {
  // Create file name
  char filename[PATH_MAX];
  snprintf(filename, sizeof(filename), "%s%s", name, ZLargePages::is_explicit() ? ".hugetlb" : "");

  // Create file
  const int extra_flags = ZLargePages::is_explicit() ? MFD_HUGETLB : 0;
  const int fd = z_memfd_create(filename, MFD_CLOEXEC | extra_flags);
  if (fd == -1) {
    ZErrno err;
    log_debug(gc, init)("Failed to create memfd file (%s)",
                        ((UseLargePages && err == EINVAL) ? "Hugepages not supported" : err.to_string()));
    return -1;
  }

  log_info(gc, init)("Heap backed by file: /memfd:%s", filename);

  return fd;
}
  • 优先创建内存文件描述符
  • 通过syscall函数调用memfd_create创建文件描述符

2.3 分级管理

与操作系统类型,为了便于管理,ZGC也定义了两级结构:虚拟内存和物理内存,分别由ZVirtualMemoryManager和ZPhysicalMemoryManager管理。

ZGC的物理内存并不是通常意义上的物理内存,而仅表示mutator使用的Java heap内存。因此仍然是用户空间虚拟内存,仅为了与Mark0、Mark1、Remapped视图区分。本文中的物理内存,如无特殊说明,特指ZGC中的物理内存。

2.3.1 物理内存的分配

zPhysicalMemory.cpp

ZPhysicalMemory ZPhysicalMemoryManager::alloc(size_t size) {
  if (unused_capacity() < size) {
    // Not enough memory available
    return ZPhysicalMemory();
  }

  _used += size;
  return _backing.alloc(size);
}
  • ZPhysicalMemory的分配通过调用ZPhysicalMemoryBacking实现

zPhysicalMemoryBacking_linux_x86.cpp

ZPhysicalMemory ZPhysicalMemoryBacking::alloc(size_t size) {
  assert(is_aligned(size, _granule_size), "Invalid size");

  ZPhysicalMemory pmem;

  // Allocate segments
  for (size_t allocated = 0; allocated < size; allocated += _granule_size) {
    const uintptr_t start = _manager.alloc_from_front(_granule_size);
    assert(start != UINTPTR_MAX, "Allocation should never fail");
    pmem.add_segment(ZPhysicalMemorySegment(start, _granule_size));
  }

  return pmem;
}
  • 物理内存以segment组织,每次分配既分配一个新的segment
  • 调用ZMemoryManager alloc_from_front从free list中分配内存area

zMemory.cpp

uintptr_t ZMemoryManager::alloc_from_front(size_t size) {
  ZListIterator iter(&_freelist);
  for (ZMemory* area; iter.next(&area);) {
    if (area->size() >= size) {
      if (area->size() == size) {
        // Exact match, remove area
        const uintptr_t start = area->start();
        _freelist.remove(area);
        delete area;
        return start;
      } else {
        // Larger than requested, shrink area
        const uintptr_t start = area->start();
        area->shrink_from_front(size);
        return start;
      }
    }
  }

  // Out of memory
  return UINTPTR_MAX;
}
  • 遍历free list
  • 如果内存段size等于申请的size,则返回当前area,并将此area从free list移除
  • 如果内存段size大于申请的size,则裁剪此area
  • 如果遍历完整个free list仍然无法分配,则触发OOM

2.3.2 物理内存的释放

zPhysicalMemory.cpp

void ZPhysicalMemoryManager::free(ZPhysicalMemory pmem) {
  _backing.free(pmem);
  _used -= pmem.size();
}
  • ZPhysicalMemory的释放通过调用ZPhysicalMemoryBacking实现

zPhysicalMemoryBacking_linux_x86.cpp

void ZPhysicalMemoryBacking::free(ZPhysicalMemory pmem) {
  const size_t nsegments = pmem.nsegments();

  // Free segments
  for (size_t i = 0; i < nsegments; i++) {
    const ZPhysicalMemorySegment segment = pmem.segment(i);
    _manager.free(segment.start(), segment.size());
  }
}
  • 调用ZMemoryManager free函数释放segment

zMemory.cpp

void ZMemoryManager::free(uintptr_t start, size_t size) {
  assert(start != UINTPTR_MAX, "Invalid address");
  const uintptr_t end = start + size;

  ZListIterator iter(&_freelist);
  for (ZMemory* area; iter.next(&area);) {
    if (start < area->start()) {
      ZMemory* const prev = _freelist.prev(area);
      if (prev != NULL && start == prev->end()) {
        if (end == area->start()) {
          // Merge with prev and current area
          prev->grow_from_back(size + area->size());
          _freelist.remove(area);
          delete area;
        } else {
          // Merge with prev area
          prev->grow_from_back(size);
        }
      } else if (end == area->start()) {
        // Merge with current area
        area->grow_from_front(size);
      } else {
        // Insert new area before current area
        assert(end < area->start(), "Areas must not overlap");
        ZMemory* new_area = new ZMemory(start, size);
        _freelist.insert_before(area, new_area);
      }

      // Done
      return;
    }
  }

  // Insert last
  ZMemory* const last = _freelist.last();
  if (last != NULL && start == last->end()) {
    // Merge with last area
    last->grow_from_back(size);
  } else {
    // Insert new area last
    ZMemory* new_area = new ZMemory(start, size);
    _freelist.insert_last(new_area);
  }
}
  • 释放物理内存时,与free list前后area比较,如果start或end相等,则合并area
  • 否则创建新的area,加入到free list中

2.3.3 物理内存的映射

ZPageAllocator在分配页时,会调用ZPhysicalMemoryManager的map函数,进而调用到ZPhysicalMemoryBacking的map函数。
zPhysicalMemoryBacking_linux_x86.cpp

void ZPhysicalMemoryBacking::map(ZPhysicalMemory pmem, uintptr_t offset) const {
  if (ZUnmapBadViews) {
    // Only map the good view, for debugging only
    map_view(pmem, ZAddress::good(offset), AlwaysPreTouch);
  } else {
    // Map all views
    map_view(pmem, ZAddress::marked0(offset), AlwaysPreTouch);
    map_view(pmem, ZAddress::marked1(offset), AlwaysPreTouch);
    map_view(pmem, ZAddress::remapped(offset), AlwaysPreTouch);
  }
}
  • 将物理内存顺序映射到marked0、marked1、remapped空间

3. 引用

OpenJDK 12 源代码

你可能感兴趣的:(JVM,OpenJDK,12,ZGC源码分析)