Source Code Analysis of DBLE Memory Management Module
This article provides a detailed analysis of DBLE's memory management module, explaining its structure, configuration parameters, allocation and recycling logic, and includes annotated source code snippets for classes such as DirectByteBufferPool and ByteBufferPage, illustrating how off‑heap and on‑heap memory are handled.
Author Introduction Lulu, a technology enthusiast who loves sharing, currently focuses on database‑related research.
Preface The article presents a two‑part overview of DBLE's memory management module: first the module's structure, then a line‑by‑line source code analysis.
Memory Management Module Structure DBLE manages memory primarily for two purposes: network read/write and query result set handling. Network I/O uses off‑heap memory (managed by DirectByteBufferPool ), while result set processing may use either off‑heap or on‑heap memory depending on query complexity.
The official memory structure diagram (image) shows the relationship between DirectByteBufferPool and physical memory. A simplified diagram is also provided for better understanding.
Key Configuration Parameters Parameters such as bufferPoolPageNumber , bufferPoolPageSize , and bufferPoolChunkSize are configured in Server.xml . By default, bufferPoolPageSize is 2 MiB, bufferPoolPageNumber equals CPU_COUNT × 20 , and bufferPoolChunkSize is 4 KiB. It is recommended to set bufferPoolPageSize as a multiple of bufferPoolChunkSize to avoid waste.
Memory Allocation Logic If no size is specified, the allocator uses a minimum unit defined by bufferPoolChunkSize . When a size is specified, the allocator rounds up to the nearest multiple of the chunk size. Allocation proceeds by scanning pages from the last allocated page + 1 to the end, then wrapping around if necessary. If no off‑heap page can satisfy the request, on‑heap memory is allocated.
Source Code: Initialization
public DirectByteBufferPool(int pageSize, short chunkSize, short pageCount) {
allPages = new ByteBufferPage[pageCount];
this.chunkSize = chunkSize;
this.pageSize = pageSize;
this.pageCount = pageCount;
prevAllocatedPage = new AtomicInteger(0);
for (int i = 0; i < pageCount; i++) {
allPages[i] = new ByteBufferPage(ByteBuffer.allocateDirect(pageSize), chunkSize);
}
memoryUsage = new ConcurrentHashMap<>();
}Source Code: ByteBufferPage Initialization
public ByteBufferPage(ByteBuffer buf, int chunkSize) {
this.chunkSize = chunkSize;
chunkCount = buf.capacity() / chunkSize;
chunkAllocateTrack = new BitSet(chunkCount);
this.buf = buf;
}Source Code: Allocation Method
public ByteBuffer allocate(int size) {
final int theChunkCount = size / chunkSize + (size % chunkSize == 0 ? 0 : 1);
int selectedPage = prevAllocatedPage.incrementAndGet() % allPages.length;
ByteBuffer byteBuf = allocateBuffer(theChunkCount, selectedPage, allPages.length);
if (byteBuf == null) {
byteBuf = allocateBuffer(theChunkCount, 0, selectedPage);
}
if (byteBuf != null) {
long threadId = Thread.currentThread().getId();
memoryUsage.merge(threadId, (long) byteBuf.capacity(), Long::sum);
} else {
return ByteBuffer.allocate(size);
}
return byteBuf;
}Source Code: Buffer Allocation Helper
private ByteBuffer allocateBuffer(int theChunkCount, int startPage, int endPage) {
for (int i = startPage; i < endPage; i++) {
ByteBuffer buffer = allPages[i].allocateChunk(theChunkCount);
if (buffer != null) {
prevAllocatedPage.getAndSet(i);
return buffer;
}
}
return null;
}Source Code: Chunk Allocation in a Page
public ByteBuffer allocateChunk(int theChunkCount) {
if (!allocLockStatus.compareAndSet(false, true)) {
return null;
}
int startChunk = -1;
int continueCount = 0;
try {
for (int i = 0; i < chunkCount; i++) {
if (!chunkAllocateTrack.get(i)) {
if (startChunk == -1) {
startChunk = i;
continueCount = 1;
if (theChunkCount == 1) break;
} else {
if (++continueCount == theChunkCount) break;
}
} else {
startChunk = -1;
continueCount = 0;
}
}
if (continueCount == theChunkCount) {
int offStart = startChunk * chunkSize;
int offEnd = offStart + theChunkCount * chunkSize;
buf.limit(offEnd);
buf.position(offStart);
ByteBuffer newBuf = buf.slice();
markChunksUsed(startChunk, theChunkCount);
return newBuf;
} else {
return null;
}
} finally {
allocLockStatus.set(false);
}
}Memory Recycling Recycling distinguishes between heap and off‑heap buffers. Heap buffers are simply cleared for GC. Off‑heap buffers locate their parent page, compute the starting chunk, and mark the corresponding chunks as free.
public void recycle(ByteBuffer theBuf) { if (!(theBuf instanceof DirectBuffer)) { theBuf.clear(); return; } long size = theBuf.capacity(); boolean recycled = false; DirectBuffer thisNavBuf = (DirectBuffer) theBuf; int chunkCount = theBuf.capacity() / chunkSize; DirectBuffer parentBuf = (DirectBuffer) thisNavBuf.attachment(); int startChunk = (int) ((thisNavBuf.address() - parentBuf.address()) / this.chunkSize); for (ByteBufferPage allPage : allPages) { if ((recycled = allPage.recycleBuffer((ByteBuffer) parentBuf, startChunk, chunkCount))) { break; } } long threadId = Thread.currentThread().getId(); memoryUsage.merge(threadId, -size, Long::sum); if (!recycled) { LOGGER.info("warning ,not recycled buffer " + theBuf); } }
Usage Examples Network read/write uses NIOSocketWR#asyncRead to allocate a buffer from the pool. Result‑set handling uses SingleNodeHandler#rowResponse to allocate a buffer for writing rows before sending them to the client.
Conclusion The article walks through DBLE's memory management module, covering its architecture, configuration, allocation, and recycling mechanisms, and provides concrete code examples to help developers understand and extend the system.
Aikesheng Open Source Community
The Aikesheng Open Source Community provides stable, enterprise‑grade MySQL open‑source tools and services, releases a premium open‑source component each year (1024), and continuously operates and maintains them.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.