前面文章說了PoolChunk如何管理Normal內存塊,本文分享PoolSubpage如何管理Small內存塊。
源碼分析基於Netty 4.1.52算法
PoolSubpage負責管理Small內存塊。一個PoolSubpage中的內存塊size都相同,該size對應SizeClasses#sizeClasses表格的一個索引index。
新建立的PoolSubpage都必須加入到PoolArena#smallSubpagePools[index]鏈表中。
PoolArena#smallSubpagePools是一個PoolSubpage數組,數組中每一個元素都是一個PoolSubpage鏈表,PoolSubpage之間能夠經過next,prev組成鏈表。
感興趣的同窗能夠參考《內存對齊類SizeClasses》。數組
注意,Small內存size並不必定小於pageSize(默認爲8K)
默認Small內存size <= 28672(28KB)
關於Normal內存塊,Small內存塊,pageSize,可參考《PoolChunk實現原理》。微信
PoolSubpage實際上就是PoolChunk中的一個Normal內存塊,大小爲其管理的內存塊size與pageSize最小公倍數。
PoolSubpage使用位圖的方式管理內存塊。
PoolSubpage#bitmap是一個long數組,其中每一個long元素上每一個bit位均可以表明一個內存塊是否使用。源碼分析
分配Small內存塊有兩個步驟this
若是PoolArena#smallSubpagePools中已經有對應的PoolSubpage緩衝,則不須要該步驟。spa
PoolChunk#allocateSubpagecode
private long allocateSubpage(int sizeIdx) { // #1 PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx); synchronized (head) { //allocate a new run // #2 int runSize = calculateRunSize(sizeIdx); //runSize must be multiples of pageSize // #3 long runHandle = allocateRun(runSize); if (runHandle < 0) { return -1; } // #4 int runOffset = runOffset(runHandle); int elemSize = arena.sizeIdx2size(sizeIdx); PoolSubpage<T> subpage = new PoolSubpage<T>(head, this, pageShifts, runOffset, runSize(pageShifts, runHandle), elemSize); subpages[runOffset] = subpage; // #5 return subpage.allocate(); } }
#1
這裏涉及修改PoolArena#smallSubpagePools中的PoolSubpage鏈表,須要同步操做#2
計算內存塊size和pageSize最小公倍數#3
分配一個Normal內存塊,做爲PoolSubpage的底層內存塊,大小爲Small內存塊size和pageSize最小公倍數#4
構建PoolSubpage
runOffset,即Normal內存塊偏移量,也是該PoolSubpage在整個Chunk中的偏移量
elemSize,Small內存塊size#5
在subpage上分配內存塊orm
PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int pageShifts, int runOffset, int runSize, int elemSize) { // #1 this.chunk = chunk; this.pageShifts = pageShifts; this.runOffset = runOffset; this.runSize = runSize; this.elemSize = elemSize; bitmap = new long[runSize >>> 6 + LOG2_QUANTUM]; // runSize / 64 / QUANTUM init(head, elemSize); } void init(PoolSubpage<T> head, int elemSize) { doNotDestroy = true; if (elemSize != 0) { // #2 maxNumElems = numAvail = runSize / elemSize; nextAvail = 0; bitmapLength = maxNumElems >>> 6; if ((maxNumElems & 63) != 0) { bitmapLength ++; } for (int i = 0; i < bitmapLength; i ++) { bitmap[i] = 0; } } // #3 addToPool(head); }
#1
bitmap長度爲runSize / 64 / QUANTUM,從《內存對齊類SizeClasses》能夠看到,runSize都是2^LOG2_QUANTUM的倍數。blog
#2
elemSize:每一個內存塊的大小
maxNumElems:內存塊數量
bitmapLength:bitmap使用的long元素個數,使用bitmap中一部分元素足以管理所有內存塊。(maxNumElems & 63) != 0
,表明maxNumElems不能整除64,因此bitmapLength要加1,用於管理餘下的內存塊。#3
添加到PoolSubpage鏈表中索引
前面分析《Netty內存池與PoolArena》中說過,在PoolArena中分配Small內存塊時,首先會從PoolArena#smallSubpagePools中查找對應的PoolSubpage。若是找到了,直接從該PoolSubpage上分配內存。不然,分配一個Normal內存塊,建立PoolSubpage,再在上面分配內存塊。
PoolSubpage#allocate
long allocate() { // #1 if (numAvail == 0 || !doNotDestroy) { return -1; } // #2 final int bitmapIdx = getNextAvail(); // #3 int q = bitmapIdx >>> 6; int r = bitmapIdx & 63; assert (bitmap[q] >>> r & 1) == 0; bitmap[q] |= 1L << r; // #4 if (-- numAvail == 0) { removeFromPool(); } // #5 return toHandle(bitmapIdx); }
#1
沒有可用內存塊,分配失敗。一般PoolSubpage分配完成後會從PoolArena#smallSubpagePools中移除,再也不在該PoolSubpage上分配內存,因此通常不會出現這種場景。#2
獲取下一個可用內存塊的bit下標#3
設置對應bit爲1,即已使用bitmapIdx >>> 6
,獲取該內存塊在bitmap數組中第q元素bitmapIdx & 63
,獲取該內存塊是bitmap數組中第q個元素的第r個bit位bitmap[q] |= 1L << r
,將bitmap數組中第q個元素的第r個bit位設置爲1,表示已經使用#4
全部內存塊已分配了,則將其從PoolArena中移除。#5
toHandle 轉換爲最終的handle
private int getNextAvail() { int nextAvail = this.nextAvail; if (nextAvail >= 0) { this.nextAvail = -1; return nextAvail; } return findNextAvail(); }
nextAvail爲初始值或free時釋放的值。
若是nextAvail存在,設置爲不可用後直接返回該值。
若是不存在,調用findNextAvail查找下一個可用內存塊。
private int findNextAvail() { final long[] bitmap = this.bitmap; final int bitmapLength = this.bitmapLength; // #1 for (int i = 0; i < bitmapLength; i ++) { long bits = bitmap[i]; if (~bits != 0) { return findNextAvail0(i, bits); } } return -1; } private int findNextAvail0(int i, long bits) { final int maxNumElems = this.maxNumElems; final int baseVal = i << 6; // #2 for (int j = 0; j < 64; j ++) { if ((bits & 1) == 0) { int val = baseVal | j; if (val < maxNumElems) { return val; } else { break; } } bits >>>= 1; } return -1; }
#1
遍歷bitmap,~bits != 0
,表示存在一個bit位不爲1,即存在可用內存塊。#2
遍歷64個bit位,(bits & 1) == 0
,檢查最低bit位是否爲0(可用),爲0則返回val。
val等於 (i << 6) | j
,即i * 64 + j
,該bit位在bitmap中是第幾個bit位。bits >>>= 1
,右移一位,處理下一個bit位。
釋放Small內存塊可能有兩個步驟
PoolSubpage#free
boolean free(PoolSubpage<T> head, int bitmapIdx) { if (elemSize == 0) { return true; } // #1 int q = bitmapIdx >>> 6; int r = bitmapIdx & 63; assert (bitmap[q] >>> r & 1) != 0; bitmap[q] ^= 1L << r; setNextAvail(bitmapIdx); // #2 if (numAvail ++ == 0) { addToPool(head); return true; } // #3 if (numAvail != maxNumElems) { return true; } else { // #4 if (prev == next) { // Do not remove if this subpage is the only one left in the pool. return true; } // #5 doNotDestroy = false; removeFromPool(); return false; } }
#1
將對應bit位設置爲可使用#2
在PoolSubpage的內存塊所有被使用時,釋放了某個內存塊,這時從新加入到PoolArena中。#3
未徹底釋放,即還存在已分配內存塊,返回true#4
邏輯到這裏,是處理全部內存塊已經徹底釋放的場景。
PoolArena#smallSubpagePools鏈表組成雙向鏈表,鏈表中只有head和當前PoolSubpage時,當前PoolSubpage的prev,next都指向head。
這時當前PoolSubpage是PoolArena中該鏈表最後一個PoolSubpage,不釋放該PoolSubpage,以便下次申請內存時直接從該PoolSubpage上分配。#5
從PoolArena中移除,並返回false,這時PoolChunk會將釋放對應Page節點。
void free(long handle, int normCapacity, ByteBuffer nioBuffer) { if (isSubpage(handle)) { // #1 int sizeIdx = arena.size2SizeIdx(normCapacity); PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx); PoolSubpage<T> subpage = subpages[runOffset(handle)]; assert subpage != null && subpage.doNotDestroy; synchronized (head) { // #2 if (subpage.free(head, bitmapIdx(handle))) { //the subpage is still used, do not free it return; } } } // #3 ... }
#1
查找head節點,同步#2
調用subpage#free釋放Small內存塊
若是subpage#free返回false,將繼續向下執行,這時會釋放PoolSubpage整個內存塊,不然,不釋放PoolSubpage內存塊。#3
釋放Normal內存塊,就是釋放PoolSubpage整個內存塊。該部份內容可參考《PoolChunk實現原理》。
若是您以爲本文不錯,歡迎關注個人微信公衆號,系列文章持續更新中。您的關注是我堅持的動力!