...
+-------------------+-------------------+
| Leader | | | |
+-------------------+-------------------+
Merging chunks
A free chunk can be merged with its buddy if that buddy is free and unsplit. This is done recursively, until either one of the partners in the chunk pair are not free and unsplit, or until the largest chunk size - root chunk size - is reached.
...
+---------+---------+-------------------+
| A | b | C |
+---------+---------+-------------------+
|
v
+-------------------+-------------------+
| A` | C |
+-------------------+-------------------+
|
v
+-------------------+-------------------+
| A`` |
+-------------------+-------------------+
Splitting chunks
To get a small chunk from a larger chunk, a large chunk can be split. Splitting happens in power-of-2 sizes. A split operation yields the desired smaller chunk as well as 1-n splinter chunks.
...
Step
+---------------------------------------+
0 | A |
+---------------------------------------+
|
v
+-------------------+-------------------+
1 | d1 | D2 | C | B |
+-------------------+-------------------+
^
Result chunk
|
v
+-------------------+-------------------+
2 | d1 | d2 | c | B |
+-------------------+-------------------+
^
Result chunk
|
v
+-------------------+-------------------+
3 | d1 | d2 | c1 | C2 | B |
+-------------------+-------------------+
^
Result chunk
How it all looks in memory
Allocated metaspace blocks (the user-level unit of allocation) reside in chunks; chunks reside in mappings called VirtualSpaceNode
, of which multiple may exist:
...
+------------------+ <--- virtual memory region
| +-------------+ | <--- chunk
| | +---------+ | | <--- block
| | | | | |
| | +---------+ | |
| | +---------+ | | <--- block
| | | | | |
| | | | | |
| | | | | |
| | +---------+ | |
| | +---------+ | | <--- block
| | +---------+ | |
| | | |
| +-------------+ | <--- end: chunk
| +-------------+ | <--- chunk
| | +---------+ | |
| | | | | |
...
+------------------+ <--- end: virtual memory region
+------------------+ <--- next virtual memory region
| +-------------+ |
| | +---------+ | |
| | | | | |
| | +---------+ | |
...
Subsystems
Metaspace implementation is divided into separate sub systems, each of which is isolated from its peers and has a small number of tasks.
The Virtual Memory Subsystem
Classes:
- RootChunkArea
and RootChunkAreaLUT
The Virtual Memory Layer is the lowest subsystem. It forms one half of a metaspace context (the upper half being the chunk manager).
It is responsible for reserving and committing memory. It knows about commit granules. Its outside interface to upper layers is the VirtualSpaceList
while some operations are also directly exposed via VirtualSpaceNode.
2.1.1. Essential operations
"Allocate new root chunk"
Metachunk* VirtualSpaceList::allocate_root_chunk();
This carves out a new root chunk from the underlying reserved space and hands it to the caller (nothing is committed yet, this is purely reserved memory).
"commit this range"
bool VirtualSpaceNode::ensure_range_is_committed(MetaWord* p, size_t word_size);
Upper layers request that a given arbitrary address range should be committed. Subsystem figures out which granules would be affected and makes sure those are committed (which may be a noop if they had been committed before).
When committing, subsystem honors VM limits (
MaxMetaspaceSize
resp. the commit gc threshold) via the commit limiter."uncommit this range"
void VirtualSpaceNode::uncommit_range(MetaWord* p, size_t word_size);
Similar to committing. Subsystem figures out which commit granules are affected, and uncommits those.
"purge"
void VirtualSpaceList::purge()
This unmaps all completely empty memory regions, and uncommits all unused commit granules.
2.1.2. Other operations
The Virtual Memory Subsystem takes care of Buddy Allocator operations, on behalf of upper regions:
- "split this chunk, recursivly"
void VirtualSpaceNode::split(chunklevel_t target_level, Metachunk* c, FreeChunkListVector* freelists);
- "merge up chunk with neighbors as far as possible"
-
Metachunk* VirtualSpaceNode::merge(Metachunk* c, FreeChunkListVector* freelists);
- "enlarge chunk in place"
bool VirtualSpaceNode::attempt_enlarge_chunk(Metachunk* c, FreeChunkListVector* freelists);
2.1.3. Classes
2.1.3.1. class VirtualSpaceList
VirtualSpaceList
is a list of reserved regions (VirtualSpaceNode). VirtualSpaceList manages a single (if non-expandable) or a series of (if expandable) virtual memory regions.
Internally it holds a list of nodes (VirtualSpaceNode), each one managing a single contiguous memory region. The first node of this list is the current node and used for allocation of new root chunks.
Beyond access to those nodes, and the ability to grow new nodes (if expandable), it allows for purging: purging this list means removing and unmapping all memory regions which are unused. Other than that, this class is unexciting.
Of this object only exist one or two global instances, contained within the one or two MetaspaceContext
values which exist globally.
2.1.3.2. class VirtualSpaceNode
VirtualSpaceNode
manages one contiguous reserved region of the Metaspace.
In case of the compressed class space, it contains the whole compressed class space, contained in a list with a single node which cannot be expanded.
It knows which granules in this region are committed (class CommitMask
).
VirtualSpaceNode also knows about root chunks: the memory is divided into a series of root-chunk-sized areas (class RootChunkArea
). This means the memory has to be aligned (both starting address and size) to root chunk area size of 4M.
| root chunk | root chunk | root chunk |
+-----------------------------------------------------+
| |
| `VirtualSpaceNode` memory |
| |
+-----------------------------------------------------+
|x| |x|x|x| | | | |x|x|x| | | |x|x| | | |x|x|x|x| | | | <-- commit granules
(x = committed)
Note: the concepts of commit granules and of root chunks and the buddy allocator are almost completely independent from each other.
2.1.3.3. class CommitMask
Very unexciting. Just a bit mask holding commit information (one bit per granule).
2.1.3.4. class RootChunkArea and class RootChunkAreaLUT
RootChunkArea
contains the buddy allocator code. It is wrapped over the area of a single root chunk. It knows how to split and merge chunks. It also has a reference to the very first chunk in this area (needed since Metachunk
chunk headers are separate entities from their payload, see below, and it is not easy to get from the metaspace start address to its Metachunk
).
A RootChunkArea
object does not exist on its own but as a part of an array within a VirtualSpaceNode
, describing the node's memory.
RootChunkAreaLUT
(for "lookup table") just holds the sequence of RootChunkArea
classes which cover the memory region of the VirtualSpaceNode
. It offers lookup functionality "give me the RootChunkArea
for this address".
2.1.3.5. class CommitLimiter
The CommitLimiter contains the limit logic we may want to impose on how much memory can be committed:
In metaspace, we have two limits to committing memory: the absolute limit, MaxMetaspaceSize; and the GC threshold. In both cases an allocation should fail if it would require committing memory and hit one of these limits.
However, the actual Metaspace allocator is a generic one and this GC- and classloading specific logic should be kept separate. Therefore it is hidden inside this interface.
This allows us to: - more easily write tests for metaspace, by providing a different implementation of the commit limiter, thus keeping test logic separate from VM state. - (potentially) use the metaspace for things other than class metadata, where different commit rules would apply.
Under normal circumstances, only one instance of the CommitLimiter
ever exists, see CommitLimiter::globalLimiter()
, which encapsulates the GC threshold and MaxMetaspace queries.