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<ZMemory> 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<ZMemory> 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 源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章