在本文中,我們將深入探討Netty中的一個基礎組件——PoolChunk,它是Netty中ByteBuf的一個關鍵實現,負責對ByteBuf進行緩存和管理。我們將從多個方面對該組件進行詳細的闡述,包括使用場景、內部實現細節以及如何調優等。
一、PoolChunk的使用場景
在Netty中,ByteBuf是用來封裝來自網路或磁碟的數據塊的Java NIO緩衝區。PoolChunk的任務是對 ByteBuf進行內存管理和緩存。通常情況下,當我們需要使用ByteBuf時,實際上是基於內存池技術從Linux系統堆內存中申請一塊內存。這時,PoolChunk就會派上用場了。
PoolChunk通過對內存進行劃分與分配,解決了內存碎片及應用程序內存泄漏的問題,在多線程並發操作下也能保證內存分配的線程安全性,是Netty中非常重要的一個組件。
二、PoolChunk的內部實現細節
PoolChunk內部主要包含四個類:PoolChunk、PoolSubpage、PoolArena、PoolThreadCache。
1. PoolChunk
PoolChunk是PoolSubpage的容器。它在初始時被劃分為多個Page,每個Page的大小為16M,然後再將Page劃分為大小均為8K的小塊Subpage,以供應用程序分配內存。
/**
* PoolChunk 表示 PoolSubpage 的容器
*/
@Sharable
@SuppressWarnings("Duplicates")
class PoolChunk implements PoolChunkMetric {
...省略部分代碼...
/**
* PoolChunk 對象的構造函數
*/
PoolChunk(PoolArena arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
...省略部分代碼...
this.subpages = newSubpageArray(maxSubpageAllocs);
...省略部分代碼...
}
...省略部分代碼...
}
2. PoolSubpage
PoolSubpage是Chunk中用來分配ByteBuf內存的最小內存單元。它是一塊大小固定的內存塊,大小由PoolChunk的maxOrder參數指定。
PoolSubpage有著非常豐富的狀態,可以按照ByteBuf是否處於訪問狀態,以及ByteBuf的容量是否超過了PoolSubpage的容量,將PoolSubpage分成了多種狀態。
/**
* 表示 PoolChunk 中的一個小塊內存。一個 Subpage 對象代表了 PoolChunk 中的大小為 pageSize 的一個連續內存塊。
*/
final class PoolSubpage implements PoolSubpageMetric {
...省略部分代碼...
/**
* Subpage佔用的內存大小
* 如果PoolSubpage不處於當前MemoryChunk中且沒有被分配,則該變數為0
*/
int elemSize;
...省略部分代碼...
/**
* PoolSubpage 所在的 PoolChunk 對象
*/
PoolChunk chunk;
/**
* Subpage 所在的 MemoryMap 中的下標
*/
int memoryMapIdx;
/**
* Subpage 在 MemoryMap 下標所對應的高64位數組中的下標
*/
int runOffset;
/**
* Subpage 分配狀態
*/
private byte[] bitmap;
private int bitmapLength;
private int nextAvail;
private int numAvail;
...省略部分代碼...
}
3. PoolArena
PoolArena是一個PoolChunk的容器。PoolArena中包含了多個PoolChunk,每個Chunk按照一定的大小分配若干個Subpage,以供應用程序申請內存。
PoolArena使用了多種技術來優化內存分配,例如使用ByteBuf的可伸縮緩存,在堆內存分配和釋放上更加高效地使用Java Concurrent API等。
/**
* 用來管理內存塊的 PoolArena 是 PoolSubpage 和 PoolChunk 的容器
*/
@SuppressWarnings("Duplicates")
abstract class PoolArena implements Resource {
...省略部分代碼...
/**
* newChunk 的具體實現
*/
protected abstract PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize);
/**
* newSubpage 的具體實現
*/
protected abstract PoolSubpage newSubpagePoolHead(int pageSize);
/**
* 獲取內存大小對應的 Subpage 序號
*/
private int tinyIdx(int normCapacity) {
return normCapacity >>> tinyShift;
}
private int smallIdx(int normCapacity) {
int tableIdx = 0;
int i = normCapacity >>> smallShift;
while (i != 0) {
i >>>= 1;
tableIdx++;
}
return tableIdx;
}
...省略部分代碼...
}
4. PoolThreadCache
PoolThreadCache是Netty用於高效管理Cache的組件,適用於多線程環境。它用於管理多個線程使用ByteBuf中的Cache。
PoolThreadCache會對內存塊進行整合,提高內存的利用率,並且不會浪費空閑內存,節省內存資源。
@SuppressWarnings("Duplicates")
final class PoolThreadCache extends AtomicInteger {
...省略部分代碼...
/**
* 嘗試從 threadLocalMap 中獲取池化緩存 PoolThreadLocal。
* 如果 PoolThreadLocal 被 GC 回收,則重新創建,並存儲到 threadLocalMap 中。
*/
static PoolThreadCache threadCache(Thread t) {
...省略部分代碼...
}
...省略部分代碼...
}
三、PoolChunk的調優方法
由於PoolChunk的內部實現非常複雜,存在著很大的調優空間。接下來,我們將介紹通過調整PoolChunk內存池的大小和Subpage大小,以及調整內存分配器使用線程池的線程數等多種方法來優化內存的使用和性能。
1. 調整PoolChunk內存池的大小
PoolChunk的內存池大小與netty.buffer.pool.chunkSize參數密切相關。這個參數默認是16M,可以通過其它工具調整。通過適當增加或減小內存池的大小,可以更好地滿足不同應用程序對內存的需求。
-Dio.netty.buffer.pool.chunkSize=16777216
或
private static final int CHUNK_SIZE = 16 << 20; // 設置為16M
2. 調整Subpage大小
Subpage大小決定了內存分配的粒度。Netty默認使用的Subpage大小是8KB,可以根據不同應用程序的要求進行調整。
-Dio.netty.buffer.page.size=8192
或
private static final int PAGE_SIZE = 8 * 1024; // 設置為8KB
3. 調整內存分配器使用的線程池的線程數
內存分配線程池的線程數量通常也會影響到Netty應用程序的性能。如果線程池中的線程數量過多,可能會導致線程切換的開銷過大,從而影響整個應用程序的性能;如果線程池中的線程數量過少,可能會導致內存分配的速度過慢。
private static final int MAX_NUM_THREAD = 16; // 內存分配線程池的最大線程數量
private static final int MIN_NUM_THREAD = 4; // 內存分配線程池的最小線程數量
private static final int AGGREGATE_THRESHOLD = 10;//維持最小線程的時間,避免NIO Selector 喚醒線程失效
EventLoopGroup bossGroup = new NioEventLoopGroup(MIN_NUM_THREAD, createWorkerThreadFactory("netty-boss"));
EventLoopGroup workerGroup = new NioEventLoopGroup(MAX_NUM_THREAD, createWorkerThreadFactory("netty-worker"), AGGREGATE_THRESHOLD);
4. 調整ByteBuf池的容量大小
最後,改變ByteBuf池的容量大小也可以影響內存分配的效率。例如,如果應用程序需要處理大塊數據,則可以增大ByteBuf池的容量,從而提高內存分配的效率;如果應用程序需要處理小塊數據,則可以減小ByteBuf池的容量,從而縮小內存分配的粒度。
/**
* 用戶指定的每個page的大小. 我們根據這個來對內存進行池化
*/
public static final int DEFAULT_PAGE_SIZE = 8 * 1024;
/**
* 池化分配器的chunk大小。
*/
public static final int DEFAULT_CHUNK_SIZE = 4 * 1024 * 1024; // Use 4 MiB chunks.
public static final int DEFAULT_MAX_CACHED_BUFFERS_PER_CHUNK = 50;
/**
* Netty使用的最多的ByteBuf緩存數目。
*/
private static final int DEFAULT_NUM_HEAP_ARENA;
private static final int DEFAULT_NUM_DIRECT_ARENA;
static {
DEFAULT_NUM_HEAP_ARENA = Math.max(1, Runtime.getRuntime().availableProcessors() * 4 / 5);
DEFAULT_NUM_DIRECT_ARENA = Math.max(1, Runtime.getRuntime().availableProcessors() * 4 / 5);
}
結語
本文深入探討了Netty中的一個基礎組件——PoolChunk,詳細介紹了PoolChunk和PoolSubpage的內部實現細節,並針對PoolChunk進行了調優,以提高Netty應用程序的性能。希望通過本文的介紹,能夠對讀者更深入地了解Netty。
原創文章,作者:SGJLL,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/374651.html