This article mainly shares Netty in PoolChunk How to manage memory .
Source code analysis is based on Netty 4.1.52
Memory management algorithm
First of all PoolChunk Memory organization .
PoolChunk The default memory size of is 16M,Netty Divide it into 2048 individual page, Every page by 8K.
PoolChunk It can be allocated Small Memory block .
Normal Memory block size must be page Multiple .
PoolChunk adopt runsAvail Field management memory block .
runsAvail yes PriorityQueue<Long> Array , among PriorityQueue Deposit is handle.
handle It can be understood as a handle , Maintain information about a memory block , It consists of the following parts
- o: runOffset , stay chunk in page Offset index , from 0 Start ,15bit
- s: size, Current location assignable page Number ,15bit
- u: isUsed, Whether to use ?, 1bit
- e: isSubpage, Whether in subpage in , 1bit
- b: bitmapIdx, Memory block in subpage Index in , be not in subpage Then for 0, 32bit
front 《 Memory alignment class SizeClasses》 The article said ,SizeClasses take sizeClasses In the table isMultipageSize by 1 A new table can be formed by taking out the rows of , This is called Page form
runsAvail The default array length is 40, Each position index Top up handle Represents the existence of a block of available memory , And distributable pageSize Greater than or equal to (pageIdx=index) Upper pageSize, Less than (pageIdex=index+1) Of pageSize.
Such as runsAvail[11] Upper handle Of size Distributable pageSize May be 16 ~ 19,
If runsAvail[11] On handle Of size by 18, If it's time to handle Allocated 7 individual page, The rest 11 individual page, At this time, we will handle Move runsAvail[8]( Of course ,handle Information needs to be adjusted ).
At this time, if you want to find the distribution 6 individual page, You can start from runsAvail[5] Start looking for runsAvail Array , If the previous runsAvail[5]~runsAvail[7] None handle, I found it runsAvail[8].
Distribute 6 individual page after , The rest 5 individual page,handle Move runsAvail[4].
Have a look first PoolChunk Constructor for
PoolChunk(PoolArena<T> arena, T memory, int pageSize, int pageShifts, int chunkSize, int maxPageIdx, int offset) {
// #1
unpooled = false;
this.arena = arena;
this.memory = memory;
this.pageSize = pageSize;
this.pageShifts = pageShifts;
this.chunkSize = chunkSize;
this.offset = offset;
freeBytes = chunkSize;
runsAvail = newRunsAvailqueueArray(maxPageIdx);
runsAvailMap = new IntObjectHashMap<Long>();
subpages = new PoolSubpage[chunkSize >> pageShifts];
// #2
int pages = chunkSize >> pageShifts;
long initHandle = (long) pages << SIZE_SHIFT;
insertAvailRun(0, pages, initHandle);
cachedNioBuffers = new ArrayDeque<ByteBuffer>(8);
}
unpooled: Whether to use memory pool
arena: The PoolChunk Of PoolArena
memory: Underlying memory block , For heap memory , It's a byte Array , For direct memory , It is (jvm)ByteBuffer, But either way , The default memory size is 16M.
pageSize:page size , The default is 8K.
chunkSize: Whole PoolChunk The memory size of , The default is 16777216, namely 16M.
offset: Underlying memory alignment offset , The default is 0.
runsAvail: initialization runsAvail
runsAvailMap: Record the start and end positions of each memory block runOffset and handle mapping .
#2
insertAvailRun Method in runsAvail Insert a handle, The handle representative page The offset is 0 Can be allocated 16M Memory block
Memory allocation
PoolChunk#allocate
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache cache) {
final long handle;
// #1
if (sizeIdx <= arena.smallMaxSizeIdx) {
// small
handle = allocateSubpage(sizeIdx);
if (handle < 0) {
return false;
}
assert isSubpage(handle);
} else {
// #2
int runSize = arena.sizeIdx2size(sizeIdx);
handle = allocateRun(runSize);
if (handle < 0) {
return false;
}
}
// #3
ByteBuffer nioBuffer = cachedNioBuffers != null? cachedNioBuffers.pollLast() : null;
initBuf(buf, nioBuffer, handle, reqCapacity, cache);
return true;
}
#1
Handle Small Memory block request , call allocateSubpage Method treatment , Analysis of subsequent articles .#2
Handle Normal Memory block request
sizeIdx2size Methods find the corresponding memory block according to the memory block index size.sizeIdx2size yes PoolArena Parent class SizeClasses Methods provided , Please refer to the series of articles 《 Memory alignment class SizeClasses》.
allocateRun Methods responsible for distribution Normal Memory block , return handle Stores the allocated memory block size and offset .
#3
Use handle And underlying memory classes (ByteBuffer) initialization ByteBuf 了 .
private long allocateRun(int runSize) {
// #1
int pages = runSize >> pageShifts;
// #2
int pageIdx = arena.pages2pageIdx(pages);
synchronized (runsAvail) {
//find first queue which has at least one big enough run
// #3
int queueIdx = runFirstBestFit(pageIdx);
if (queueIdx == -1) {
return -1;
}
//get run with min offset in this queue
PriorityQueue<Long> queue = runsAvail[queueIdx];
long handle = queue.poll();
assert !isUsed(handle);
// #4
removeAvailRun(queue, handle);
// #5
if (handle != -1) {
handle = splitLargeRun(handle, pages);
}
// #6
freeBytes -= runSize(pageShifts, handle);
return handle;
}
}
#1
Calculate what is needed page Number #2
Calculate the corresponding pageIdx
Be careful ,pages2pageIdx Method aligns the requested memory size to the above Page One of the tables size. For example, apply for 172032 byte (21 individual page) Memory block ,pages2pageIdx Methods the calculation results are as follows 13, Actual distribution 196608(24 individual page) Memory block .#3
from pageIdx To traverse the runsAvail, Find the first handle.
The handle The required memory blocks can be allocated on .#4
from runsAvail,runsAvailMap Remove the handle Information #5
stay #3
Step found handle Partition the required memory block on the .#6
Reduce the number of bytes of available memory
private long splitLargeRun(long handle, int needPages) {
assert needPages > 0;
// #1
int totalPages = runPages(handle);
assert needPages <= totalPages;
int remPages = totalPages - needPages;
// #2
if (remPages > 0) {
int runOffset = runOffset(handle);
// keep track of trailing unused pages for later use
int availOffset = runOffset + needPages;
long availRun = toRunHandle(availOffset, remPages, 0);
insertAvailRun(availOffset, remPages, availRun);
// not avail
return toRunHandle(runOffset, needPages, 1);
}
//mark it as used
handle |= 1L << IS_USED_SHIFT;
return handle;
}
#1
totalPages, from handle Get the current location available in page Count .
remPages, Surplus after distribution page Count .#2
The remaining page Number greater than 0
availOffset, Calculate surplus page Start offset
Make a new one handle,availRun
insertAvailRun take availRun Insert into runsAvail,runsAvailMap in
Memory free
void free(long handle, int normCapacity, ByteBuffer nioBuffer) {
...
// #1
int pages = runPages(handle);
synchronized (runsAvail) {
// collapse continuous runs, successfully collapsed runs
// will be removed from runsAvail and runsAvailMap
// #2
long finalRun = collapseRuns(handle);
// #3
finalRun &= ~(1L << IS_USED_SHIFT);
//if it is a subpage, set it to run
finalRun &= ~(1L << IS_SUBPAGE_SHIFT);
insertAvailRun(runOffset(finalRun), runPages(finalRun), finalRun);
freeBytes += pages << pageShifts;
}
if (nioBuffer != null && cachedNioBuffers != null &&
cachedNioBuffers.size() < PooledByteBufAllocator.DEFAULT_MAX_CACHED_BYTEBUFFERS_PER_CHUNK) {
cachedNioBuffers.offer(nioBuffer);
}
}
#1
Calculate released page Count #2
If possible , Merge the available memory blocks before and after #3
Insert a new handle
collapseRuns
private long collapseRuns(long handle) {
return collapseNext(collapsePast(handle));
}
collapsePast Method merges the previous blocks of available memory
collapseNext Method to merge the available memory blocks
private long collapseNext(long handle) {
for (;;) {
// #1
int runOffset = runOffset(handle);
int runPages = runPages(handle);
Long nextRun = getAvailRunByOffset(runOffset + runPages);
if (nextRun == null) {
return handle;
}
int nextOffset = runOffset(nextRun);
int nextPages = runPages(nextRun);
//is continuous
// #2
if (nextRun != handle && runOffset + runPages == nextOffset) {
//remove next run
removeAvailRun(nextRun);
handle = toRunHandle(runOffset, runPages + nextPages, 0);
} else {
return handle;
}
}
}
#1
getAvailRunByOffset Methods from runsAvailMap Of the next memory block found in handle.#2
If it is a contiguous block of memory , The next memory block is removed handle, And its page Merge to generate a new handle.
Here's an example
You can combine the examples runsAvail And memory usage changes , Understand the code above .
actually ,2 individual Page The memory block of the Subpage Distribute , When it is recycled, it is put back into the thread cache instead of releasing the memory block directly , But to show PoolChunk Memory management process in , These scenarios are not considered in the figure .
PoolChunk stay Netty 4.1.52 Version changes the algorithm , Introduced jemalloc 4 The algorithm of -- https://github.com/netty/nett...
Netty 4.1.52 Previous version ,PoolChunk What is introduced is jemalloc 3 The algorithm of , Using binary tree to manage memory block . Interested students can refer to my subsequent articles 《PoolChunk Realization (jemalloc 3 The algorithm of )》
If you think this article is good , Welcome to my WeChat official account. , The series is being updated . Your concern is the driving force of my persistence !