Netty分布式ByteBuf使用page級別的內(nèi)存分配解析
前面小節(jié)我們剖析過命中緩存的內(nèi)存分配邏輯, 前提是如果緩存中有數(shù)據(jù), 那么緩存中沒有數(shù)據(jù), netty是如何開辟一塊內(nèi)存進行內(nèi)存分配的呢?這一小節(jié)帶大家進行剖析:
netty內(nèi)存分配數(shù)據(jù)結(jié)構(gòu)
之前我們介紹過, netty內(nèi)存分配的單位是chunk, 一個chunk的大小是16MB, 實際上每個chunk, 都以雙向鏈表的形式保存在一個chunkList中, 而多個chunkList, 同樣也是雙向鏈表進行關(guān)聯(lián)的, 大概結(jié)構(gòu)如下所示:

在chunkList中, 是根據(jù)chunk的內(nèi)存使用率歸到一個chunkList中, 這樣, 在內(nèi)存分配時, 會根據(jù)百分比找到相應的chunkList, 在chunkList中選擇一個chunk進行內(nèi)存分配
我們看PoolArena中有關(guān)chunkList的成員變量
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;
這里總共定義了6個chunkList, 并在構(gòu)造方法將其進行初始化
跟到其構(gòu)造方法中:
protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
//代碼省略
q100 = new PoolChunkList<T>(null, 100, Integer.MAX_VALUE, chunkSize);
q075 = new PoolChunkList<T>(q100, 75, 100, chunkSize);
q050 = new PoolChunkList<T>(q075, 50, 100, chunkSize);
q025 = new PoolChunkList<T>(q050, 25, 75, chunkSize);
q000 = new PoolChunkList<T>(q025, 1, 50, chunkSize);
qInit = new PoolChunkList<T>(q000, Integer.MIN_VALUE, 25, chunkSize);
//用雙向鏈表的方式進行連接
q100.prevList(q075);
q075.prevList(q050);
q050.prevList(q025);
q025.prevList(q000);
q000.prevList(null);
qInit.prevList(qInit);
//代碼省略
}首先通過new PoolChunkList()這種方式將每個chunkList進行創(chuàng)建, 我們以 q050 = new PoolChunkList<T>(q075, 50, 100, chunkSize) 為例進行簡單的介紹
q075表示當前q50的下一個節(jié)點是q075, 剛才我們講過ChunkList是通過雙向鏈表進行關(guān)聯(lián)的, 所以這里不難理解
參數(shù)50和100表示當前chunkList中存儲的chunk的內(nèi)存使用率都在50%到100%之間, 最后chunkSize為其設(shè)置大小
創(chuàng)建完ChunkList之后, 再設(shè)置其上一個節(jié)點, q050.prevList(q025)為例, 這里代表當前chunkList的上一個節(jié)點是q025
以這種方式創(chuàng)建完成之后, chunkList的節(jié)點關(guān)系變成了如下圖所示:

netty中, chunk又包含了多個page, 每個page的大小為8k, 如果要分配16k的內(nèi)存, 則在在chunk中找到連續(xù)的兩個page就可以分配, 對應關(guān)系如下:

很多場景下, 為緩沖區(qū)分配8k的內(nèi)存也是一種浪費, 比如只需要分配2k的緩沖區(qū), 如果使用8k會造成6k的浪費, 這種情況, netty又會將page切分成多個subpage, 每個subpage大小要根據(jù)分配的緩沖區(qū)大小而指定, 比如要分配2k的內(nèi)存, 就會將一個page切分成4個subpage, 每個subpage的大小為2k, 如圖:

我們看PoolSubpage的屬性
final PoolChunk<T> chunk; private final int memoryMapIdx; private final int runOffset; private final int pageSize; private final long[] bitmap; PoolSubpage<T> prev; PoolSubpage<T> next; boolean doNotDestroy; int elemSize;
chunk代表其子頁屬于哪個chunk
bitmap用于記錄子頁的內(nèi)存分配情況
prev和next, 代表子頁是按照雙向鏈表進行關(guān)聯(lián)的, 這里分別指向上一個和下一個節(jié)點
elemSize屬性, 代表的就是這個子頁是按照多大內(nèi)存進行劃分的, 如果按照1k劃分, 則可以劃分出8個子頁
簡單介紹了內(nèi)存分配的數(shù)據(jù)結(jié)構(gòu), 我們開始剖析netty在page級別上分配內(nèi)存的流程:
我們回到PoolArena的allocate方法
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
//規(guī)格化
final int normCapacity = normalizeCapacity(reqCapacity);
if (isTinyOrSmall(normCapacity)) {
int tableIdx;
PoolSubpage<T>[] table;
//判斷是不是tinty
boolean tiny = isTiny(normCapacity);
if (tiny) { // < 512
//緩存分配
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
return;
}
//通過tinyIdx拿到tableIdx
tableIdx = tinyIdx(normCapacity);
//subpage的數(shù)組
table = tinySubpagePools;
} else {
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
return;
}
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
//拿到對應的節(jié)點
final PoolSubpage<T> head = table[tableIdx];
synchronized (head) {
final PoolSubpage<T> s = head.next;
//默認情況下, head的next也是自身
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
long handle = s.allocate();
assert handle >= 0;
s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
if (tiny) {
allocationsTiny.increment();
} else {
allocationsSmall.increment();
}
return;
}
}
allocateNormal(buf, reqCapacity, normCapacity);
return;
}
if (normCapacity <= chunkSize) {
//首先在緩存上進行內(nèi)存分配
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
//分配成功, 返回
return;
}
//分配不成功, 做實際的內(nèi)存分配
allocateNormal(buf, reqCapacity, normCapacity);
} else {
//大于這個值, 就不在緩存上分配
allocateHuge(buf, reqCapacity);
}
}我們之前講過, 如果在緩存中分配不成功, 則會開辟一塊連續(xù)的內(nèi)存進行緩沖區(qū)分配, 這里我們先跳過isTinyOrSmall(normCapacity)往后的代碼, 下一小節(jié)進行分析
首先 if (normCapacity <= chunkSize) 說明其小于16MB, 然后首先在緩存中分配, 因為最初緩存中沒有值, 所以會走到allocateNormal(buf, reqCapacity, normCapacity), 這里實際上就是在page級別上進行分配, 分配一個或者多個page的空間
我們跟進allocateNormal
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
//首先在原來的chunk上進行內(nèi)存分配(1)
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)) {
++allocationsNormal;
return;
}
//創(chuàng)建chunk進行內(nèi)存分配(2)
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
++allocationsNormal;
assert handle > 0;
//初始化byteBuf(3)
c.initBuf(buf, handle, reqCapacity);
qInit.add(c);
}這里主要拆解了如下步驟
1. 在原有的chunk中進行分配
2. 創(chuàng)建chunk進行分配
3. 初始化ByteBuf
首先我們看第一步, 在原有的chunk中進行分配:
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)) {
++allocationsNormal;
return;
}我們之前講過, chunkList是存儲不同內(nèi)存使用量的chunk集合, 每個chunkList通過雙向鏈表的形式進行關(guān)聯(lián), 這里的q050.allocate(buf, reqCapacity, normCapacity)就代表首先在q050這個chunkList上進行內(nèi)存分配
我們以q050為例進行分析, 跟到q050.allocate(buf, reqCapacity, normCapacity)方法中:
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
if (head == null || normCapacity > maxCapacity) {
return false;
}
//從head節(jié)點往下遍歷
for (PoolChunk<T> cur = head;;) {
long handle = cur.allocate(normCapacity);
if (handle < 0) {
cur = cur.next;
if (cur == null) {
return false;
}
} else {
cur.initBuf(buf, handle, reqCapacity);
if (cur.usage() >= maxUsage) {
remove(cur);
nextList.add(cur);
}
return true;
}
}
}首先會從head節(jié)點往下遍歷
long handle = cur.allocate(normCapacity)
表示對于每個chunk, 都嘗試去分配
if (handle < 0) 說明沒有分配到, 則通過cur = cur.next找到下一個節(jié)點繼續(xù)進行分配, 我們講過chunk也是通過雙向鏈表進行關(guān)聯(lián)的, 所以對這塊邏輯應該不會陌生
如果handle大于0說明已經(jīng)分配到了內(nèi)存, 則通過cur.initBuf(buf, handle, reqCapacity)對byteBuf進行初始化
if (cur.usage() >= maxUsage) 代表當前chunk的內(nèi)存使用率大于其最大使用率, 則通過remove(cur)從當前的chunkList中移除, 再通過nextList.add(cur)添加到下一個chunkList中
我們再回到PoolArena的allocateNormal方法中:
我們看第二步
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize)
這里的參數(shù)pageSize是8192, 也就是8k
maxOrder為11
pageShifts為13, 2的13次方正好是8192, 也就是8k
chunkSize為16777216, 也就是16MB
這里的參數(shù)值可以通過debug的方式跟蹤到
因為我們的示例是堆外內(nèi)存, newChunk(pageSize, maxOrder, pageShifts, chunkSize)所以會走到DirectArena的newChunk方法中:
protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
return new PoolChunk<ByteBuffer>(
this, allocateDirect(chunkSize),
pageSize, maxOrder, pageShifts, chunkSize);
}這里直接通過構(gòu)造函數(shù)創(chuàng)建了一個chunk
allocateDirect(chunkSize)這里是通過jdk的api的申請了一塊直接內(nèi)存, 我們跟到PoolChunk的構(gòu)造函數(shù)中:
PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
unpooled = false;
this.arena = arena;
//memeory為一個ByteBuf
this.memory = memory;
//8k
this.pageSize = pageSize;
//13
this.pageShifts = pageShifts;
//11
this.maxOrder = maxOrder;
this.chunkSize = chunkSize;
unusable = (byte) (maxOrder + 1);
log2ChunkSize = log2(chunkSize);
subpageOverflowMask = ~(pageSize - 1);
freeBytes = chunkSize;
assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
maxSubpageAllocs = 1 << maxOrder;
//節(jié)點數(shù)量為4096
memoryMap = new byte[maxSubpageAllocs << 1];
//也是4096個節(jié)點
depthMap = new byte[memoryMap.length];
int memoryMapIndex = 1;
//d相當于一個深度, 賦值的內(nèi)容代表當前節(jié)點的深度
for (int d = 0; d <= maxOrder; ++ d) {
int depth = 1 << d;
for (int p = 0; p < depth; ++ p) {
memoryMap[memoryMapIndex] = (byte) d;
depthMap[memoryMapIndex] = (byte) d;
memoryMapIndex ++;
}
}
subpages = newSubpageArray(maxSubpageAllocs);
}首先將參數(shù)傳入的值進行賦值
this.memory = memory 就是將參數(shù)中創(chuàng)建的堆外內(nèi)存進行保存, 就是chunk所指向的那塊連續(xù)的內(nèi)存, 在這個chunk中所分配的ByteBuf, 都會在這塊內(nèi)存中進行讀寫
我們重點關(guān)注 memoryMap = new byte[maxSubpageAllocs << 1]
和 depthMap = new byte[memoryMap.length] 這兩步
首先看 memoryMap = new byte[maxSubpageAllocs << 1]
這里初始化了一個字節(jié)數(shù)組memoryMap, 大小為maxSubpageAllocs << 1, 也就是4096
depthMap = new byte[memoryMap.length] 同樣也是初始化了一個字節(jié)數(shù)組, 大小為memoryMap的大小, 也就是4096
繼續(xù)往下分析之前, 我們看chunk的一個層級關(guān)系

這是一個二叉樹的結(jié)構(gòu), 左側(cè)的數(shù)字代表層級, 右側(cè)代表一塊連續(xù)的內(nèi)存, 每個父節(jié)點下又拆分成多個子節(jié)點, 最頂層表示的內(nèi)存范圍為0-16MB, 其又下分為兩層, 范圍為0-8MB, 8-16MB, 以此類推, 最后到11層, 以8k的大小劃分, 也就是一個page的大小
如果我們分配一個8mb的緩沖區(qū), 則會將第二層的第一個節(jié)點, 也就是0-8這個連續(xù)的內(nèi)存進行分配, 分配完成之后, 會將這個節(jié)點設(shè)置為不可用, 具體邏輯后面會講解
結(jié)合上面的圖, 我們再看構(gòu)造方法中的for循環(huán):
for (int d = 0; d <= maxOrder; ++ d) {
int depth = 1 << d;
for (int p = 0; p < depth; ++ p) {
memoryMap[memoryMapIndex] = (byte) d;
depthMap[memoryMapIndex] = (byte) d;
memoryMapIndex ++;
}
}實際上這個for循環(huán)就是將上面的結(jié)構(gòu)包裝成一個字節(jié)數(shù)組memoryMap, 外層循環(huán)用于控制層數(shù), 內(nèi)層循環(huán)用于控制里面每層的節(jié)點, 這里經(jīng)過循環(huán)之后, memoryMap和depthMap內(nèi)容為以下表現(xiàn)形式:
[0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4...........]
這里注意一下, 因為程序中數(shù)組的下標是從1開始設(shè)置的, 所以第零個節(jié)點元素為默認值0
這里數(shù)字代表層級, 同時也代表了當前層級的節(jié)點, 相同的數(shù)字個數(shù)就是這一層級的節(jié)點數(shù)
其中0為2個(因為這里分配時下標是從1開始的, 所以第0個位置是默認值0, 實際上第零層元素只有一個, 就是頭結(jié)點), 1為2個, 2為4個, 3為8個, 4為16個, n為2的n次方個, 直到11, 也就是11有2的11次方個
我們再回到PoolArena的allocateNormal方法中
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
//首先在原來的chunk上進行內(nèi)存分配(1)
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)) {
++allocationsNormal;
return;
}
//創(chuàng)建chunk進行內(nèi)存分配(2)
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
++allocationsNormal;
assert handle > 0;
//初始化byteBuf(3)
c.initBuf(buf, handle, reqCapacity);
qInit.add(c);
}我們繼續(xù)剖析 long handle = c.allocate(normCapacity) 這步
跟到allocate(normCapacity)中
long allocate(int normCapacity) {
if ((normCapacity & subpageOverflowMask) != 0) {
return allocateRun(normCapacity);
} else {
return allocateSubpage(normCapacity);
}
}如果分配是以page為單位, 則走到allocateRun(normCapacity)方法中, 跟進去:
private long allocateRun(int normCapacity) {
int d = maxOrder - (log2(normCapacity) - pageShifts);
int id = allocateNode(d);
if (id < 0) {
return id;
}
freeBytes -= runLength(id);
return id;
}int d = maxOrder - (log2(normCapacity) - pageShifts) 表示根據(jù)normCapacity計算出圖5-8-5中的第幾層
int id = allocateNode(d) 表示根據(jù)層級關(guān)系, 去分配一個節(jié)點, 其中id代表memoryMap中的下標
我們跟到allocateNode方法中
private int allocateNode(int d) {
//下標初始值為1
int id = 1;
//代表當前層級第一個節(jié)點的初始下標
int initial = - (1 << d);
//獲取第一個節(jié)點的值
byte val = value(id);
//如果值大于層級, 說明chunk不可用
if (val > d) {
return -1;
}
//當前下標對應的節(jié)點值如果小于層級, 或者當前下標小于層級的初始下標
while (val < d || (id & initial) == 0) {
//當前下標乘以2, 代表下當前節(jié)點的子節(jié)點的起始位置
id <<= 1;
//獲得id位置的值
val = value(id);
//如果當前節(jié)點值大于層數(shù)(節(jié)點不可用)
if (val > d) {
//id為偶數(shù)則+1, id為奇數(shù)則-1(拿的是其兄弟節(jié)點)
id ^= 1;
//獲取id的值
val = value(id);
}
}
byte value = value(id);
assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
value, id & initial, d);
//將找到的節(jié)點設(shè)置為不可用
setValue(id, unusable);
//逐層往上標記被使用
updateParentsAlloc(id);
return id;
}這里是實際上是從第一個節(jié)點往下找, 找到層級為d未被使用的節(jié)點, 我們可以通過注釋體會其邏輯
找到相關(guān)節(jié)點后通過setValue將當前節(jié)點設(shè)置為不可用, 其中id是當前節(jié)點的下標, unusable代表一個不可用的值, 這里是12, 因為我們的層級只有12層, 所以設(shè)置為12之后就相當于標記不可用
設(shè)置成不可用之后, 通過updateParentsAlloc(id)逐層設(shè)置為被使用
我們跟進updateParentsAlloc方法
private void updateParentsAlloc(int id) {
while (id > 1) {
//取到當前節(jié)點的父節(jié)點的id
int parentId = id >>> 1;
//獲取當前節(jié)點的值
byte val1 = value(id);
//找到當前節(jié)點的兄弟節(jié)點
byte val2 = value(id ^ 1);
//如果當前節(jié)點值小于兄弟節(jié)點, 則保存當前節(jié)點值到val, 否則, 保存兄弟節(jié)點值到val
//如果當前節(jié)點是不可用, 則當前節(jié)點值是12, 大于兄弟節(jié)點的值, 所以這里將兄弟節(jié)點的值進行保存
byte val = val1 < val2 ? val1 : val2;
//將val的值設(shè)置為父節(jié)點下標所對應的值
setValue(parentId, val);
//id設(shè)置為父節(jié)點id, 繼續(xù)循環(huán)
id = parentId;
}
}這里其實是將循環(huán)將兄弟節(jié)點的值替換成父節(jié)點的值, 我們可以通過注釋仔細的進行邏輯分析
如果實在理解有困難, 我通過畫圖幫助大家理解:
簡單起見, 我們這里只設(shè)置三層:

這里我們模擬其分配場景, 假設(shè)只有三層, 其中index代表數(shù)組memoryMap的下標, value代表其值, memoryMap中的值就為[0, 0, 1, 1, 2, 2, 2, 2]
我們要分配一個4MB的byteBuf, 在我們調(diào)用allocateNode(int d)中傳入的d是2, 也就是第二層
根據(jù)我們上面分分析的邏輯這里會找到第二層的第一個節(jié)點, 也就是0-4mb這個節(jié)點, 找到之后將其設(shè)置為不可用, 這樣memoryMap中的值就為[0, 0, 1, 1, 12, 2, 2, 2]
二叉樹的結(jié)構(gòu)就會變?yōu)?

注意標紅部分, 將index為4的節(jié)點設(shè)置為了不可用
將這個節(jié)點設(shè)置為不可用之后, 則會將進行向上設(shè)置不可用, 循環(huán)將兄弟節(jié)點數(shù)值較小的節(jié)點替換到父節(jié)點, 也就是將index為2的節(jié)點的值替換成了index的為5的節(jié)點的值, 這樣數(shù)組的值就會變?yōu)閇0, 1, 2, 1, 12, 2, 2, 2]
二叉樹的結(jié)構(gòu)變?yōu)?

注意, 這里節(jié)點標紅僅僅代表節(jié)點變化, 并不是當前節(jié)點為不可用狀態(tài), 真正不可用狀態(tài)的判斷依據(jù)是value值為12
這樣, 如果再次分配一個4MB內(nèi)存的ByteBuf, 根據(jù)其邏輯, 則會找到第二層的第二個節(jié)點, 也就是4-8MB
再根據(jù)我們的邏輯, 通過向上設(shè)置不可用, index為2就會設(shè)置成不可用狀態(tài), 將value的值設(shè)置為12, 數(shù)組數(shù)值變?yōu)閇0, 1, 12, 1, 12, 12, 2, 2]二叉樹如下圖所示:

這樣我們看到, 通過分配兩個4mb的byteBuf之后, 當前節(jié)點和其父節(jié)點都會設(shè)置成不可用狀態(tài), 當index=2的節(jié)點設(shè)置為不可用之后, 將不會再找這個節(jié)點下的子節(jié)點
以此類推, 直到所有的內(nèi)存分配完畢的時候, index為1的節(jié)點, 也會變成不可用狀態(tài), 這樣所有的page就分配完畢, chunk中再無可用節(jié)點
我們再回到PoolArena的allocateNormal方法中
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
//首先在原來的chunk上進行內(nèi)存分配(1)
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)) {
++allocationsNormal;
return;
}
//創(chuàng)建chunk進行內(nèi)存分配(2)
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
++allocationsNormal;
assert handle > 0;
//初始化byteBuf(3)
c.initBuf(buf, handle, reqCapacity);
qInit.add(c);
}通過以上邏輯我們知道, long handle = c.allocate(normCapacity)這一步, 其實返回的就是memoryMap的一個下標, 通過這個下標, 我們能唯一的定位一塊內(nèi)存
繼續(xù)往下跟, 通過c.initBuf(buf, handle, reqCapacity)初始化ByteBuf之后, 通過qInit.add(c)將新創(chuàng)建的chunk添加到chunkList中
我們跟到initBuf方法中去
void initBuf(PooledByteBuf<T> buf, long handle, int reqCapacity) {
int memoryMapIdx = memoryMapIdx(handle);
int bitmapIdx = bitmapIdx(handle);
if (bitmapIdx == 0) {
byte val = value(memoryMapIdx);
assert val == unusable : String.valueOf(val);
buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx),
arena.parent.threadCache());
} else {
initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity);
}
}這里通過memoryMapIdx(handle)找到memoryMap的下標, 其實就是handle的值
bitmapIdx(handle)是有關(guān)subPage中使用到的邏輯, 如果是page級別的分配, 這里只返回0, 所以進入到if塊中
if中首先斷言當前節(jié)點是不是不可用狀態(tài), 然后通過init方法進行初始化
其中runOffset(memoryMapIdx)表示偏移量, 偏移量相當于分配給緩沖區(qū)的這塊內(nèi)存相對于chunk中申請的內(nèi)存的首地址偏移了多少
參數(shù)runLength(memoryMapIdx), 表示根據(jù)下標獲取可分配的最大長度
我們跟到init中, 這里會走到PooledByteBuf的init方法中:
void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
//初始化
assert handle >= 0;
assert chunk != null;
//在哪一塊內(nèi)存上進行分配的
this.chunk = chunk;
//這一塊內(nèi)存上的哪一塊連續(xù)內(nèi)存
this.handle = handle;
memory = chunk.memory;
this.offset = offset;
this.length = length;
this.maxLength = maxLength;
tmpNioBuf = null;
this.cache = cache;
}這里又是我們熟悉的部分, 將屬性進行了初始化
以上就是完整的DirectUnsafePooledByteBuf在page級別的完整分配的流程, 邏輯也是非常的復雜, 想真正的掌握熟練, 也需要多下功夫進行調(diào)試和剖析,更多關(guān)于Netty分布式ByteBuf使用page級別內(nèi)存分配的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
netty服務端處理請求聯(lián)合pipeline分析
這篇文章主要為大家介紹了netty服務端處理請求聯(lián)合pipeline示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
java ant包中的org.apache.tools.zip實現(xiàn)壓縮和解壓縮實例詳解
這篇文章主要介紹了java ant包中的org.apache.tools.zip實現(xiàn)壓縮和解壓縮實例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
SpringBoot讀取自定義配置文件方式(properties,yaml)
這篇文章主要介紹了SpringBoot讀取自定義配置文件方式(properties,yaml),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
如何利用Spring?Boot?監(jiān)控?SQL?運行情況
這篇文章主要介紹了如何利用Spring?Boot監(jiān)控SQL運行情況,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07

