PoolArena
在PooledByteBufAllocate初始化最后一步,就是初始化PoolArena。PoolArena是對(duì)內(nèi)存申請(qǐng)和釋放的一個(gè)抽象。在類層次上,PoolArena有2個(gè)子類DirectArena和HeapArena,并且將內(nèi)存處理的工作都統(tǒng)一抽象到了PoolArena中。PoolArena主要管理的對(duì)象是tinySubpagePools、smallSubpagePools和PoolChunkList,前兩者在PoolSubpage一節(jié)已有提及,而PoolChunkList則是一組鏈表對(duì)象,并且會(huì)根據(jù)PoolChunk內(nèi)存使用率的變化將其中的元素進(jìn)行轉(zhuǎn)移。
PoolArena成員介紹
通過PoolArena的構(gòu)造函數(shù)看一下常用成員變量,這里忽略了metric相關(guān)的變量。
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 = newSubpagePoolArray(numTinySubpagePools);
for (int i = 0; i < tinySubpagePools.length; i ++) {
tinySubpagePools[i] = newSubpagePoolHead(pageSize);
}
numSmallSubpagePools = pageShifts - 9;
smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
for (int i = 0; i < smallSubpagePools.length; i ++) {
smallSubpagePools[i] = newSubpagePoolHead(pageSize);
}
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);
構(gòu)造函數(shù)前半段是一些熟悉的屬性——pageSize、maxOrder等,忽略掉這些屬性設(shè)置外,PoolArena主要的成員變量就是tinySubpagePools和smallSubpagePools,這2個(gè)在PoolSubpage一小節(jié)已經(jīng)講述過,這里看一下PoolChunkList。
PoolChunkList
為了理解PoolChunkList,我們先看一下它的構(gòu)造函數(shù)。
PoolChunkList實(shí)例化
PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage, int chunkSize) {
this.arena = arena;
this.nextList = nextList;
this.minUsage = minUsage;
this.maxUsage = maxUsage;
maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
}
private static int calculateMaxCapacity(int minUsage, int chunkSize) {
minUsage = Math.max(1, minUsage);
if (minUsage == 100) {
return 0;
}
return (int) (chunkSize * (100L - minUsage) / 100L);
}
在構(gòu)造函數(shù)中定義了PoolChunkList中元素的內(nèi)存最小、最大使用率,并且根據(jù)最小占用率計(jì)算出最大內(nèi)存容量。此外由nextList還可以看出來(lái),PoolChunkList除了自身是一條PoolChunk的鏈表外,不同的PoolChunkList還會(huì)形成一個(gè)鏈表。除了上述幾個(gè)成員變量外,PoolChunkList還有head和prevList,前者指向PoolChunk的最新加入的節(jié)點(diǎn),后者是PoolChunkList的前繼節(jié)點(diǎn)。
PoolChunkList添加PoolChunk
接著看一下PoolChunkList添加PoolChunk的add方法。add方法在3種情況被調(diào)用:
- 新創(chuàng)建的Chunk,由一個(gè)特殊的PoolChunkList——qInit調(diào)用,加入鏈表中。
- 在chunk內(nèi)存使用率變化時(shí),需要調(diào)整其所在鏈表時(shí)調(diào)用
- 調(diào)整鏈表時(shí),發(fā)現(xiàn)當(dāng)前PoolChunkList不滿足內(nèi)存使用率,遞歸調(diào)用。
void add(PoolChunk<T> chunk) {
if (chunk.usage() >= maxUsage) {
nextList.add(chunk);
return;
}
add0(chunk);
}
void add0(PoolChunk<T> chunk) {
chunk.parent = this;
if (head == null) {
head = chunk;
chunk.prev = null;
chunk.next = null;
} else {
chunk.prev = null;
chunk.next = head;
head.prev = chunk;
head = chunk;
}
}
add方法首先計(jì)算傳入的chunk的使用率,如果使用率超過了當(dāng)前PoolChunkList的最大使用率,則遞歸調(diào)用add方法,直到尋找到合適的PoolChunkList后,調(diào)用add0。add0就是流程化的雙向鏈表頭插法添加操作。
PoolChunkList移動(dòng)PoolChunk
與add相反的是move操作,在free內(nèi)存或者調(diào)整內(nèi)存使用率時(shí),move0方法被調(diào)用,它會(huì)根據(jù)chunk的使用率將其從高使用率的PoolChunkList移動(dòng)到低使用率的PoolChunkList中。
private boolean move0(PoolChunk<T> chunk) {
if (prevList == null) {
assert chunk.usage() == 0;
return false;
}
return prevList.move(chunk);
}
private boolean move(PoolChunk<T> chunk) {
if (chunk.usage() < minUsage) {
return move0(chunk);
}
add0(chunk);
return true;
}
move0這里首先進(jìn)行判斷,如果當(dāng)前節(jié)點(diǎn)為0,意味著chunk是從q000鏈表移除,而q000的最小使用率為1%,所以這里做了一個(gè)斷言chunk使用率為0。之后調(diào)用prevList的move方法。
move方法也是先判斷使用率是否小于調(diào)用者最小使用率,若小于則再次調(diào)用move0方法,否則將chunk添加到調(diào)用該方法的PoolChunkList中。
PoolChunkList申請(qǐng)內(nèi)存
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
if (normCapacity > maxCapacity) {
return false;
}
for (PoolChunk<T> cur = head; cur != null; cur = cur.next) {
if (cur.allocate(buf, reqCapacity, normCapacity)) {
if (cur.usage() >= maxUsage) {
remove(cur);
nextList.add(cur);
}
return true;
}
}
return false;
}
首先注意到有2個(gè)capacity的入?yún)?,一個(gè)是reqCapacity,表示外部請(qǐng)求申請(qǐng)的內(nèi)存大小,一個(gè)是normCapacity,表示經(jīng)過規(guī)整化為2的冪次方的內(nèi)存大小,也是實(shí)際申請(qǐng)的內(nèi)存大小,所以在與PoolChunkList的maxCapacity判斷時(shí)用的maxCapacity。
滿足內(nèi)存大小申請(qǐng)條件后,用當(dāng)前PoolChunkList內(nèi)部的PoolChunk鏈表一個(gè)個(gè)嘗試申請(qǐng)。如果在某個(gè)PoolChunk申請(qǐng)內(nèi)存后,發(fā)現(xiàn)其內(nèi)存使用率超過了當(dāng)前PoolChunkList的最大使用率,則將其轉(zhuǎn)移到下一個(gè)PoolChunkList中去。
PoolChunkList初始化
回到PoolArena中,這里初始化了6個(gè)PoolChunkList,組成了一個(gè)q000 <-> q025 <-> q050 <-> q075 <-> q100的雙向鏈表。qInit比較特殊,他的后繼節(jié)點(diǎn)是q000,但前繼節(jié)點(diǎn)是自身。從名字上也可以看出,qInit負(fù)責(zé)創(chuàng)建PoolChunk,且在PoolChunk使用率超過qInit的maxUsage(25%)后,將其轉(zhuǎn)移到q000鏈表去。因?yàn)閝000最小使用率就是1%,再小就沒必要留著PoolChunk了,因此q000的前繼節(jié)點(diǎn)為null。
PoolArena申請(qǐng)內(nèi)存
外界通過調(diào)用allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity)方法來(lái)申請(qǐng)內(nèi)存。
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
PooledByteBuf<T> buf = newByteBuf(maxCapacity);
allocate(cache, buf, reqCapacity);
return buf;
}
這個(gè)方法首先調(diào)用了一個(gè)抽象方法newByteBuf(int maxCapacity)獲取到PooledByteBuf,然后將申請(qǐng)的內(nèi)存通過的allocate方法放入這個(gè)ByteBuf中。
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
final int normCapacity = normalizeCapacity(reqCapacity);
if (isTinyOrSmall(normCapacity)) {
int tableIdx;
PoolSubpage<T>[] table;
boolean tiny = isTiny(normCapacity);
if (tiny) {
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
return;
}
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;
} else {
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
return;
}
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
final PoolSubpage<T> head = table[tableIdx];
synchronized (head) {
final PoolSubpage<T> s = head.next;
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
long handle = s.allocate();
assert handle >= 0;
s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
incTinySmallAllocation(tiny);
return;
}
}
synchronized (this) {
allocateNormal(buf, reqCapacity, normCapacity);
}
incTinySmallAllocation(tiny);
return;
}
if (normCapacity <= chunkSize) {
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
return;
}
synchronized (this) {
allocateNormal(buf, reqCapacity, normCapacity);
++allocationsNormal;
}
} else {
allocateHuge(buf, reqCapacity);
}
}
可以看出這個(gè)方法是相當(dāng)?shù)拈L(zhǎng),但總體邏輯還算清晰,慣例先分步驟:
- 將申請(qǐng)內(nèi)存規(guī)整化。
- 申請(qǐng)tiny、small級(jí)別的內(nèi)存。
- 申請(qǐng)normal級(jí)別的內(nèi)存。
- 申請(qǐng)huge級(jí)別的內(nèi)存。
將申請(qǐng)內(nèi)存規(guī)整化。
將內(nèi)存大小規(guī)整化這個(gè)概念前面多次提到,他的作用是將內(nèi)存大小向上對(duì)齊。對(duì)于512B以下大小的內(nèi)存,對(duì)齊到大于申請(qǐng)內(nèi)存的下一個(gè)16B的倍數(shù)。比如15B,會(huì)對(duì)齊到16B,41B會(huì)對(duì)齊到48B。對(duì)于大于512B的內(nèi)存,對(duì)齊到大于申請(qǐng)內(nèi)存的下一個(gè)2的冪次方,比如申請(qǐng)600B,會(huì)對(duì)齊到1KB,申請(qǐng)3KB,會(huì)對(duì)齊到4KB。
它的實(shí)現(xiàn)大量使用位運(yùn)算,代碼如下。由于directMemoryCacheAlignment默認(rèn)為0,且通常也不會(huì)設(shè)置,這里忽略了。
int normalizeCapacity(int reqCapacity) {
checkPositiveOrZero(reqCapacity, "reqCapacity");
if (reqCapacity >= chunkSize) {
return reqCapacity;
}
if (!isTiny(reqCapacity)) {
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;
}
if ((reqCapacity & 15) == 0) {
return reqCapacity;
}
return (reqCapacity & ~15) + 16;
}
首先檢驗(yàn)了內(nèi)存是否不是負(fù)數(shù),否則直接拋出異常。之后判斷申請(qǐng)內(nèi)存是否超出了ChunkSize級(jí)別,即16MB,若超出這個(gè)大小,則達(dá)到了huge級(jí)別,無(wú)需規(guī)整化。
若不是tiny級(jí)別內(nèi)存,則先自減,分別與自身無(wú)符號(hào)右移1、2、4、8、16位做或運(yùn)算,再自增。完成后還做了是否越界的判斷,越界時(shí),無(wú)符號(hào)右移一位。
比如600B,二進(jìn)制為0010 0101 1000,自減后變成0010 0101 0111,做完無(wú)符號(hào)右移后,變成0011 1111 1111,自增后,變成0100 0000 0000,即2^10=1024B。
若為tiny級(jí)別,&15==0表明剛好是16的倍數(shù),直接返回。否則對(duì)15取反后,與內(nèi)存做與運(yùn)算,再加上16。因?yàn)?5取反后,低4位為0,高位全部為1,相當(dāng)于掩碼。
申請(qǐng)tiny、small級(jí)別的內(nèi)存
在對(duì)內(nèi)存規(guī)整化以后,首先需要判斷它在哪個(gè)范圍。對(duì)tiny、small級(jí)別來(lái)說(shuō)采用的是 (subpageOverflowMask & normCapacity) == 0 這樣一個(gè)判斷條件,其中subpageOverflowMask = ~(pageSize - 1),默認(rèn)低13位為0,其余高位為1。之后又使用 (normCapacity & 0xFFFFFE00) == 0 進(jìn)一步劃分tiny和small。tiny、small,包括后續(xù)的norm,都會(huì)先嘗試用緩存分配,如果分配成功則直接返回。緩存分配留待后續(xù)。
在緩存分配失敗后,會(huì)去相應(yīng)的PoolArena中的PoolSubpage數(shù)組定位到對(duì)應(yīng)大小PoolSubpage的head結(jié)點(diǎn)。
在分配之前,先對(duì)head加鎖,因?yàn)榇藭r(shí)PoolChunk.allocateSubpage和PoolChunk.free可能會(huì)并發(fā)修改PoolSubpage鏈表。
加鎖完成進(jìn)入臨界區(qū),迭代head的下一個(gè)非空PoolSubpage節(jié)點(diǎn),調(diào)用其allocate方法進(jìn)行內(nèi)存分配。
在分配完成后,還需要調(diào)用其所在PoolChunk的initBufWithSubpage方法,最終會(huì)調(diào)用ByteBuf.init方法進(jìn)行初始化。分配完成后,增加對(duì)應(yīng)的分配計(jì)數(shù)器
若不存在非空的PoolSubpage節(jié)點(diǎn),則還是需要在normal分配。
申請(qǐng)normal級(jí)別的內(nèi)存
在分配normal級(jí)別的內(nèi)存時(shí),需要對(duì)PoolArena對(duì)象加鎖,防止其他線程同時(shí)分配內(nèi)存。具體分配的代碼如下
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;
}
// Add a new chunk.
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
boolean success = c.allocate(buf, reqCapacity, normCapacity);
assert success;
qInit.add(c);
}
首先嘗試在5個(gè)PoolChunkList中分配內(nèi)存。PoolChunkList分配內(nèi)存的相關(guān)代碼在前文已經(jīng)涉及。它最終會(huì)將分配的PoolChunk放在合適使用率的PoolChunkList中。
若PoolChunkList中沒有分配成功(比如在初始狀態(tài)下,PoolChunkList中除了head節(jié)點(diǎn)沒有其他PoolChunk對(duì)象),在會(huì)新增一個(gè)PoolChunk,調(diào)用新增PoolChunk分配內(nèi)存。分配完成并且初始化后,將PoolChunk加入qInit鏈表中,并移動(dòng)到符合使用率的鏈表中。
分配成功后,會(huì)在PoolArena中將對(duì)應(yīng)計(jì)數(shù)器自增。
申請(qǐng)huge級(jí)別內(nèi)存
由于huge級(jí)別的內(nèi)存過大,不適合池化管理,所以申請(qǐng)的是unpooled的內(nèi)存。申請(qǐng)完huge內(nèi)存后,依然進(jìn)行初始化和計(jì)數(shù)。
private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);
activeBytesHuge.add(chunk.chunkSize());
buf.initUnpooled(chunk, reqCapacity);
allocationsHuge.increment();
}