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空間