上一節(jié)講述了jemalloc的思想,本節(jié)將分析Netty的實(shí)現(xiàn)細(xì)節(jié)吃媒。在Netty實(shí)現(xiàn)中,相關(guān)的類(lèi)都加上了前綴Pool
吕喘,比如PoolArena
、PoolChunk
等,本節(jié)分析PoolArena
的源碼實(shí)現(xiàn)細(xì)節(jié)树枫。
首先看類(lèi)簽名:
abstract class PoolArena<T> implements PoolArenaMetric
該類(lèi)是一個(gè)抽象類(lèi)档址,這是因?yàn)?code>ByteBuf分為Heap和Direct,所以PoolArena
同樣分為兩類(lèi):Heap和Direct闻察。該類(lèi)實(shí)現(xiàn)的接口PoolArenaMetric
是一些信息的測(cè)度統(tǒng)計(jì)拱礁,忽略這些信息不再分析。
其中的關(guān)鍵成員變量如下:
private final int maxOrder; // chunk相關(guān)滿二叉樹(shù)的高度
final int pageSize; // 單個(gè)page的大小
final int pageShifts; // 用于輔助計(jì)算
final int chunkSize; // chunk的大小
final int subpageOverflowMask; // 用于判斷請(qǐng)求是否為Small/Tiny
final int numSmallSubpagePools; // small請(qǐng)求的雙向鏈表頭個(gè)數(shù)
final int directMemoryCacheAlignment; // 對(duì)齊基準(zhǔn)
final int directMemoryCacheAlignmentMask; // 用于對(duì)齊內(nèi)存
private final PoolSubpage<T>[] tinySubpagePools; // Subpage雙向鏈表
private final PoolSubpage<T>[] smallSubpagePools; // Subpage雙向鏈表
final PooledByteBufAllocator parent;
對(duì)于前述分析的如QINIT辕漂、Q0等chunk狀態(tài)呢灶,Netty使用PoolChunkList
作為容器存放相同狀態(tài)的Chunk塊,相關(guān)變量如下:
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;
構(gòu)造方法如下:
protected PoolArena(PooledByteBufAllocator parent, int pageSize,
int maxOrder, int pageShifts, int chunkSize, int cacheAlignment) {
this.parent = parent;
this.pageSize = pageSize;
this.maxOrder = maxOrder;
this.pageShifts = pageShifts;
this.chunkSize = chunkSize;
directMemoryCacheAlignment = cacheAlignment;
directMemoryCacheAlignmentMask = cacheAlignment - 1;
subpageOverflowMask = ~(pageSize - 1);
tinySubpagePools = new PoolSubpage[numTinySubpagePools];
for (int i = 0; i < tinySubpagePools.length; i ++) {
tinySubpagePools[i] = newSubpagePoolHead(pageSize);
}
numSmallSubpagePools = pageShifts - 9;
smallSubpagePools = new PoolSubpage[numSmallSubpagePools];
for (int i = 0; i < smallSubpagePools.length; i ++) {
smallSubpagePools[i] = newSubpagePoolHead(pageSize);
}
initPoolChunkList();
}
private PoolSubpage<T> newSubpagePoolHead(int pageSize) {
PoolSubpage<T> head = new PoolSubpage<T>(pageSize);
head.prev = head;
head.next = head;
return head;
}
其中initPoolChunkList()
如下:
q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, chunkSize);
q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize);
q100.prevList(q075);
q075.prevList(q050);
q050.prevList(q025);
q025.prevList(q000);
q000.prevList(null);
qInit.prevList(qInit);
這段代碼實(shí)現(xiàn)如下圖所示的雙向鏈表:
Netty使用一個(gè)枚舉來(lái)表示每次請(qǐng)求大小的類(lèi)別:
enum SizeClass {
Tiny,
Small,
Normal
// 除此之外的請(qǐng)求為Huge
}
根據(jù)請(qǐng)求分配大小判斷所屬分類(lèi)的代碼如下钉嘹,體會(huì)其中的位運(yùn)算:
// capacity < pageSize
boolean isTinyOrSmall(int normCapacity) {
// subpageOverflowMask = ~(pageSize - 1)
return (normCapacity & subpageOverflowMask) == 0;
}
// normCapacity < 512
static boolean isTiny(int normCapacity) {
return (normCapacity & 0xFFFFFE00) == 0;
}
// capacity <= chunkSize
boolean isNormal(int normCapacity){
return normCapacity <= chunkSize;
}
對(duì)容量進(jìn)行規(guī)范化的代碼如下:
int normalizeCapacity(int reqCapacity) {
// Huge 直接返回(直接內(nèi)存需要對(duì)齊)
if (reqCapacity >= chunkSize) {
return directMemoryCacheAlignment == 0 ? reqCapacity :
alignCapacity(reqCapacity);
}
// Small和Normal 規(guī)范化到大于2的n次方的最小值
if (!isTiny(reqCapacity)) { // >= 512
int normalizedCapacity = reqCapacity;
normalizedCapacity --;
normalizedCapacity |= normalizedCapacity >>> 1;
normalizedCapacity |= normalizedCapacity >>> 2;
normalizedCapacity |= normalizedCapacity >>> 4;
normalizedCapacity |= normalizedCapacity >>> 8;
normalizedCapacity |= normalizedCapacity >>> 16;
normalizedCapacity ++;
if (normalizedCapacity < 0) {
normalizedCapacity >>>= 1;
}
return normalizedCapacity;
}
// Tiny且直接內(nèi)存需要對(duì)齊
if (directMemoryCacheAlignment > 0) {
return alignCapacity(reqCapacity);
}
// Tiny且已經(jīng)是16B的倍數(shù)
if ((reqCapacity & 15) == 0) {
return reqCapacity;
}
// Tiny不是16B的倍數(shù)則規(guī)范化到16B的倍數(shù)
return (reqCapacity & ~15) + 16;
}
規(guī)范化的結(jié)果可查看請(qǐng)求分類(lèi)圖填抬,實(shí)現(xiàn)中使用了大量位運(yùn)算,請(qǐng)仔細(xì)體會(huì)隧期。另外飒责,直接內(nèi)存對(duì)齊后的請(qǐng)求容量為基準(zhǔn)的倍數(shù),比如基準(zhǔn)為64B仆潮,則分配的內(nèi)存都需要為64B的整數(shù)倍宏蛉,也就是常說(shuō)的按64字節(jié)對(duì)齊,實(shí)現(xiàn)代碼如下(依然使用位運(yùn)算):
int alignCapacity(int reqCapacity) {
// directMemoryCacheAlignmentMask = cacheAlignment - 1;
int delta = reqCapacity & directMemoryCacheAlignmentMask;
return delta == 0 ? reqCapacity : reqCapacity + directMemoryCacheAlignment - delta;
}
對(duì)于Small和Tiny的請(qǐng)求性置,隨著請(qǐng)求的分配拾并,PoolArena
可能會(huì)形成如下的雙向循環(huán)鏈表:
其中的每個(gè)節(jié)點(diǎn)都是
PoolSubpage
,在jemalloc的介紹中,說(shuō)明Subpage會(huì)以第一次請(qǐng)求分配的大小為基準(zhǔn)劃分嗅义,之后也只能進(jìn)行這個(gè)基準(zhǔn)大小的內(nèi)存分配屏歹。在PoolArena
中繼續(xù)對(duì)PoolSubpage
進(jìn)行分組,將相同基準(zhǔn)的PoolSubpage
連接成為雙向循環(huán)鏈表之碗,便于管理和內(nèi)存分配蝙眶。需要注意的是鏈表頭結(jié)點(diǎn)head是一個(gè)特殊的PoolSubpage
,不進(jìn)行實(shí)際的內(nèi)存分配任務(wù)褪那。得到鏈表head節(jié)點(diǎn)的代碼如下:
PoolSubpage<T> findSubpagePoolHead(int elemSize) {
int tableIdx;
PoolSubpage<T>[] table;
if (isTiny(elemSize)) { // < 512 Tiny
tableIdx = elemSize >>> 4;
table = tinySubpagePools;
} else { // Small
tableIdx = 0;
elemSize >>>= 10; // 512=0, 1KB=1, 2KB=2, 4KB=3
while (elemSize != 0) {
elemSize >>>= 1;
tableIdx ++;
}
table = smallSubpagePools;
}
return table[tableIdx];
}
明白了這些幽纷,繼續(xù)分析重要的內(nèi)存分配方法allocate()
:
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf,
final int reqCapacity) {
// 規(guī)范化請(qǐng)求容量
final int normCapacity = normalizeCapacity(reqCapacity);
// capacity < pageSize, Tiny/Small請(qǐng)求
if (isTinyOrSmall(normCapacity)) {
int tableIdx;
PoolSubpage<T>[] table;
boolean tiny = isTiny(normCapacity);
if (tiny) { // < 512 Tiny請(qǐng)求
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
return; // 嘗試從ThreadCache進(jìn)行分配
}
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;
} else { // Small請(qǐng)求
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
return; // 嘗試從ThreadCache進(jìn)行分配
}
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
// 分組的Subpage雙向鏈表的頭結(jié)點(diǎn)
final PoolSubpage<T> head = table[tableIdx];
synchronized (head) { // 鎖定防止其他操作修改head結(jié)點(diǎn)
final PoolSubpage<T> s = head.next;
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
long handle = s.allocate(); // 進(jìn)行分配
assert handle >= 0;
s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
return;
}
}
synchronized (this) {
// 雙向循環(huán)鏈表還沒(méi)初始化,使用normal分配
allocateNormal(buf, reqCapacity, normCapacity);
}
return;
}
// Normal請(qǐng)求
if (normCapacity <= chunkSize) {
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
return; // 嘗試從ThreadCache進(jìn)行分配
}
synchronized (this) {
allocateNormal(buf, reqCapacity, normCapacity);
}
} else {
// Huge請(qǐng)求直接分配
allocateHuge(buf, reqCapacity);
}
}
對(duì)于Normal和Huge的分配細(xì)節(jié)如下:
private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
if (q050.allocate(buf, reqCapacity, normCapacity) ||
q025.allocate(buf, reqCapacity, normCapacity) ||
q000.allocate(buf, reqCapacity, normCapacity) ||
qInit.allocate(buf, reqCapacity, normCapacity) ||
q075.allocate(buf, reqCapacity, normCapacity)) {
return;
}
// 無(wú)Chunk或已存Chunk不能滿足分配博敬,新增一個(gè)Chunk
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
assert handle > 0;
c.initBuf(buf, handle, reqCapacity);
qInit.add(c); // Chunk初始狀態(tài)為QINIT
}
private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);
buf.initUnpooled(chunk, reqCapacity);
}
總結(jié)一下內(nèi)存分配過(guò)程:
- 對(duì)于Tiny/Small友浸、Normal大小的請(qǐng)求,優(yōu)先從線程緩存中分配偏窝。
- 沒(méi)有從緩存中得到分配的Tiny/Small請(qǐng)求收恢,會(huì)從以第一次請(qǐng)求大小為基準(zhǔn)進(jìn)行分組的Subpage雙向鏈表中進(jìn)行分配;如果雙向鏈表還沒(méi)初始化祭往,則會(huì)使用Normal請(qǐng)求分配Chunk塊中的一個(gè)Page派诬,Page以請(qǐng)求大小為基準(zhǔn)進(jìn)行切分并分配第一塊內(nèi)存,然后加入到雙向鏈表中链沼。
- 沒(méi)有從緩存中得到分配的Normal請(qǐng)求默赂,則會(huì)使用伙伴算法分配滿足要求的連續(xù)Page塊。
- 對(duì)于Huge請(qǐng)求括勺,則直接使用Unpooled直接分配缆八。
內(nèi)存分配過(guò)程分析完畢,接著分析內(nèi)存釋放:
void free(PoolChunk<T> chunk, long handle, int normCapacity, PoolThreadCache cache) {
if (chunk.unpooled) { // Huge
int size = chunk.chunkSize();
destroyChunk(chunk); // 模板方法疾捍,子類(lèi)實(shí)現(xiàn)具體釋放過(guò)程
} else { // Normal, Small/Tiny
SizeClass sizeClass = sizeClass(normCapacity);
if (cache != null && cache.add(this, chunk, handle, normCapacity, sizeClass)) {
return; // 可以緩存則不釋放
}
// 否則釋放
freeChunk(chunk, handle, sizeClass);
}
}
void freeChunk(PoolChunk<T> chunk, long handle, SizeClass sizeClass) {
final boolean destroyChunk;
synchronized (this) {
// parent為所屬的chunkList奈辰,destroyChunk為true表示Chunk內(nèi)存使用裝填
// 從QINIT->Q0->...->Q0,最后釋放
destroyChunk = !chunk.parent.free(chunk, handle);
}
if (destroyChunk) {
destroyChunk(chunk); // 模板方法乱豆,子類(lèi)實(shí)現(xiàn)具體釋放過(guò)程
}
}
需要注意的是finalize()
奖恰,該方法是Object
中的方法,在對(duì)象被GC回收時(shí)調(diào)用宛裕,可知在該方法中需要清理資源瑟啃,本類(lèi)中主要清理內(nèi)存,代碼如下:
protected final void finalize() throws Throwable {
try {
super.finalize();
} finally {
destroyPoolSubPages(smallSubpagePools);
destroyPoolSubPages(tinySubpagePools);
destroyPoolChunkLists(qInit, q000, q025, q050, q075, q100);
}
}
private static void destroyPoolSubPages(PoolSubpage<?>[] pages) {
for (PoolSubpage<?> page : pages) {
page.destroy();
}
}
private void destroyPoolChunkLists(PoolChunkList<T>... chunkLists) {
for (PoolChunkList<T> chunkList: chunkLists) {
chunkList.destroy(this);
}
}
此外揩尸,當(dāng)PooledByteBuf
容量擴(kuò)增時(shí)蛹屿,內(nèi)存需要重新分配,代碼如下:
void reallocate(PooledByteBuf<T> buf, int newCapacity, boolean freeOldMemory) {
int oldCapacity = buf.length;
if (oldCapacity == newCapacity) {
return;
}
PoolChunk<T> oldChunk = buf.chunk;
long oldHandle = buf.handle;
T oldMemory = buf.memory;
int oldOffset = buf.offset;
int oldMaxLength = buf.maxLength;
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex();
// 分配新內(nèi)存
allocate(parent.threadCache(), buf, newCapacity);
// 將老數(shù)據(jù)copy到新內(nèi)存
if (newCapacity > oldCapacity) {
memoryCopy(oldMemory, oldOffset,
buf.memory, buf.offset, oldCapacity);
} else if (newCapacity < oldCapacity) {
if (readerIndex < newCapacity) {
if (writerIndex > newCapacity) {
writerIndex = newCapacity;
}
memoryCopy(oldMemory, oldOffset + readerIndex,
buf.memory, buf.offset + readerIndex, writerIndex - readerIndex);
} else {
readerIndex = writerIndex = newCapacity;
}
}
// 重新設(shè)置讀寫(xiě)索引
buf.setIndex(readerIndex, writerIndex);
// 如有必要岩榆,釋放老的內(nèi)存
if (freeOldMemory) {
free(oldChunk, oldHandle, oldMaxLength, buf.cache);
}
}
最后错负,由于該類(lèi)是一個(gè)抽象類(lèi)坟瓢,其中的抽象方法如下:
// 新建一個(gè)Chunk,Tiny/Small犹撒,Normal請(qǐng)求請(qǐng)求分配時(shí)調(diào)用
protected abstract PoolChunk<T> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize);
// 新建一個(gè)Chunk折联,Huge請(qǐng)求分配時(shí)調(diào)用
protected abstract PoolChunk<T> newUnpooledChunk(int capacity);
//
protected abstract PooledByteBuf<T> newByteBuf(int maxCapacity);
// 復(fù)制內(nèi)存,當(dāng)ByteBuf擴(kuò)充容量時(shí)調(diào)用
protected abstract void memoryCopy(T src, int srcOffset, T dst, int dstOffset, int length);
// 銷(xiāo)毀Chunk识颊,釋放內(nèi)存時(shí)調(diào)用
protected abstract void destroyChunk(PoolChunk<T> chunk);
// 判斷子類(lèi)實(shí)現(xiàn)Heap還是Direct
protectted abstract boolean isDirect();
該類(lèi)的兩個(gè)子類(lèi)分別是HeapArena
和DirectArena
诚镰,根據(jù)底層不同而實(shí)現(xiàn)不同的抽象方法。方法簡(jiǎn)單易懂谊囚,不再列出代碼怕享。
相關(guān)鏈接: