The core idea of Netty memory allocation is to maximize the use of existing memory and reduce memory fragmentation.

Netty allocates memory using poolarena.allocate () as an entry point.

First, memory is normalized by the normalizeCapacity() method, and the specification is a multiple of the first 16 bytes greater than the current value when the required memory allocation is less than 512 bytes, and an exponential power of the first 2 greater than the current value when the required memory allocation is greater than 512 bytes and less than 16MB, If the value is larger than 16MB, align the memory directly.

As an example, 32 bytes of memory are allocated when 26 bytes are required. When 618 bytes of memory is required, 1024 bytes are allocated,

final int normCapacity = normalizeCapacity(reqCapacity); 
Copy the code

Then, determine whether the memory size is smaller than 8KB according to the memory usage

if (isTinyOrSmall(normCapacity)) { // capacity < pageSize 
Copy the code

Next, the allocation of less than 8KB is maintained through the PoolSubpage array, which is divided into two classes:

The tinySubpagePools array is used to allocate and manage memory smaller than 512 bytes. The default size is 32 bytes. The array elements are poolSubPages that can form bidirectionally linked lists. The PoolSubpage with subscript 1 is used to allocate and manage 16 bytes of memory. 8KB / 16B = 512, that is, 512 memory blocks. For every 1 increase in the array subscript, the managed memory size increases by 16 bytes. Similarly, PoolSubpage, subscript 2, is used to allocate and manage 32 bytes of memory, which is 256 memory blocks. PoolSubpage internally uses bitmaps to record memory block usage.

The overall structure is shown as follows:

SmallSubpagePools array: Used to allocate memory greater than or equal to 512 bytes, default length 4.

Each PoolSubpage manages a total memory size of 8KB. The subscript 0 PoolSubpage allocates 512 bytes of memory. 8KB / 512B = 16. The size of managed memory doubles for each increment of the array index. Similarly, PoolSubpage, subscript 1, is used to allocate and manage 1KB of memory, which has eight memory blocks. PoolSubpage internally uses bitmaps to record memory block usage.

The overall structure is shown as follows:

Now let’s look at the memory allocation branch that is greater than or equal to 8KB.

ChunkSize = 16MB; chunkSize = 16MB;

if (normCapacity <= chunkSize) {
Copy the code

The next step is to allocate less than 16MB of memory. The code snippet is as follows:

So what exactly are Q050, Q025, Q000, qInit and Q075?

private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
Copy the code

What is PoolChunk in the PoolChunkList collection?

PoolChunk allocates and reallocates memory by PoolChunk. By default, PoolChunk allocates 16MB of memory, including 8KB of page nodes under PoolChunk. Of course, PoolChunk is also responsible for allocating 8KB or more of memory.

The PoolChunk class uses two arrays, depthMap and memoryMap, to represent the binary tree. Both arrays are 4096 in length. DepthMap stores the height of the binary tree, and memoryMap stores the memory allocation. It is also shared by its children. The memory size of the root node of the tree is 16M, the memory size of the two child nodes is 8M each, and the four child nodes of the next layer are 4M each, and so on. The 11th layer of the tree has 2048 8K leaf nodes, namely page.

As shown in the figure:

When initialized, the values of depthMap and memoryMap elements are exactly the same, for example:

depthMap[1025] = memoryMap [1025] = 10

Unlike depthMap, which does not change after initialization, memoryMap changes with node allocation, not only its own node changes, but also the values of its ancestors.

Here’s an example:

If 16KB of memory is allocated and a node is available at layer 10, assume that memoryMap [1025] = 10, which means that it and all of its children can be allocated, change its value from 10 to 12 (the maximum height of a number is 11), which means that it is not available. At the same time, the values of its ancestor nodes are cascaded as appropriate.

If memoryMap [1025] = 11, it is not itself available for allocation, but there are free nodes available for allocation at layer 11, in which case additional nodes will continue to be required at layer 10.

If a memoryMap [1025] = 12, it and all of its children are full and cannot be allocated, in which case additional available nodes will continue to be required at tier 10.

Let’s take a look at PoolChunkList, which manages the life cycle of poolChunks and maintains multiple Poolchunks as a bidirectional linked list, as well as a bidirectional linked list between poolChunkLists.

As shown in the figure:

The life cycle of PoolChunk is not fully bound to a PoolChunkList, but moves back and forth across poolChunkLists as memory is allocated and released.

If PoolChunk allocates memory through allocate(), its memory usage increases, and if it exceeds maxUsage, it moves from the current PoolChunkList to a later PoolChunkList.

If the PoolChunk is freed using free(), the PoolChunkList is moved from the current PoolChunkList to the previous PoolChunkList when the memory usage falls below minUsage.

Let’s go back to the previous question, what are Q050, Q025, Q000, qInit and Q075?

QInit: PoolChunkList consisting of poolchunks whose memory usage ranges from 0 to 25%

Q000: PoolChunkList of poolchunks whose memory usage ranges from 1 to 50%

Q025: PoolChunkList of poolchunks whose memory usage is between 25 and 75%

Q050: PoolChunkList of poolchunks whose memory usage ranges from 50 to 100%

Q075: PoolChunkList of poolchunks whose memory usage ranges from 75 to 100%

Q100: PoolChunkList of poolChunks whose memory usage is 100%

So, why allocate memory in this order?

This is a compromise. We want a high success rate of memory allocation and a high overall memory utilization to ensure that the relatively free memory can be reclaimed as much as possible.

Therefore, the PoolChunkList of Q050 is allocated first to keep memory utilization at a high level without making the success rate of memory allocation too low. If q050 fails, it is more likely to guarantee the success rate of memory allocation, so q025 — >q000 — >qInit is allocated, and Q075 is placed last because the PoolChunkList has a low success rate.

Then we look at the last logical branch, which is memory allocation greater than 16MB.

For large memory allocation processing is the simplest, directly through the allocateHuge() method, according to the required memory size to apply for and use.

The full text.