Netty源碼分析:PooledByteBufAllocator
無論是我們使用語句ByteBuf byteBuf = Unpooled.buffer(256);
來分配buf,還是使用如下的語句來分配Buf:
PooledByteBufAllocator allocator = new PooledByteBufAllocator(false);
ByteBuf byteBuf = allocator.heapBuffer(15);
都是使用了 PooledByteBufAllocator 這個類類分配Buf。因此就來分析下這個類。
1、常量的說明
public class PooledByteBufAllocator extends AbstractByteBufAllocator {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PooledByteBufAllocator.class);
//默認的PoolArena個數,堆內存類型
private static final int DEFAULT_NUM_HEAP_ARENA;
//默認的PoolArena個數,直接內存類型
private static final int DEFAULT_NUM_DIRECT_ARENA;
//默認的Page的個數,最小爲4K,默認爲8K
private static final int DEFAULT_PAGE_SIZE;
/*
由於每個chunk中的page是用平衡二叉樹映射管理每個PoolSubpage是否被分配,
maxOrder爲樹的深度,深度爲maxOrder層的節點數量爲 1 << maxOrder。
*/
private static final int DEFAULT_MAX_ORDER; //默認爲 11 //默認的tiny cache 的大小
private static final int DEFAULT_TINY_CACHE_SIZE; //512
//默認的small cache的大小
private static final int DEFAULT_SMALL_CACHE_SIZE;// 256
//默認的normal cache的大小
private static final int DEFAULT_NORMAL_CACHE_SIZE;//64
private static final int DEFAULT_MAX_CACHED_BUFFER_CAPACITY;
private static final int DEFAULT_CACHE_TRIM_INTERVAL;
//page容量的最小值,爲4K。
private static final int MIN_PAGE_SIZE = 4096;
//最大chunk的大小,等於2的30次方,即1G。
private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2);
以上這些常量除了最後兩個都是在如下的static塊中進行初始化。
static {
int defaultPageSize = SystemPropertyUtil.getInt("io.netty.allocator.pageSize", 8192);
Throwable pageSizeFallbackCause = null;
try {
validateAndCalculatePageShifts(defaultPageSize);
} catch (Throwable t) {
pageSizeFallbackCause = t;
defaultPageSize = 8192;
}
DEFAULT_PAGE_SIZE = defaultPageSize;
int defaultMaxOrder = SystemPropertyUtil.getInt("io.netty.allocator.maxOrder", 11);
Throwable maxOrderFallbackCause = null;
try {
validateAndCalculateChunkSize(DEFAULT_PAGE_SIZE, defaultMaxOrder);
} catch (Throwable t) {
maxOrderFallbackCause = t;
defaultMaxOrder = 11;
}
DEFAULT_MAX_ORDER = defaultMaxOrder;
// Determine reasonable default for nHeapArena and nDirectArena.
// Assuming each arena has 3 chunks, the pool should not consume more than 50% of max memory.
final Runtime runtime = Runtime.getRuntime();
final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numHeapArenas",
(int) Math.min(
runtime.availableProcessors(),
Runtime.getRuntime().maxMemory() / defaultChunkSize / 2 / 3)));
DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numDirectArenas",
(int) Math.min(
runtime.availableProcessors(),
PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));
// cache sizes
DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.tinyCacheSize", 512);
DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.smallCacheSize", 256);
DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.normalCacheSize", 64);
// 32 kb is the default maximum capacity of the cached buffer. Similar to what is explained in
// 'Scalable memory allocation using jemalloc'
DEFAULT_MAX_CACHED_BUFFER_CAPACITY = SystemPropertyUtil.getInt(
"io.netty.allocator.maxCachedBufferCapacity", 32 * 1024);
// the number of threshold of allocations when cached entries will be freed up if not frequently used
DEFAULT_CACHE_TRIM_INTERVAL = SystemPropertyUtil.getInt(
"io.netty.allocator.cacheTrimInterval", 8192);
//省略了部分日誌輸出代碼
}
從中可以得到如下的信息
1、首先是對DEFAULT_PAGE_SIZE進行初始化,默認是8K,用戶可以通過設置io.netty.allocator.pageSize來設置。
2、validateAndCalculatePageShifts函數用來檢查pageSize是否大於MIN_PAGE_SIZE
(4K)且是2的冪次方。
3、對樹的深度DEFAULT_MAX_ORDER進行初始化,默認是11,用戶可以通過io.netty.allocator.maxOrder來進行設置。
4、初始化默認chunk的大小,爲PageSize * (2 的 maxOrder冪)。
defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER
5、 計算PoolAreana的個數,PoolArena默認爲: cpu核心線程數 與 最大堆內存/2/(3*chunkSize) 這兩個數中的較小者。這裏的除以2是爲了確保系統分配的所有PoolArena佔用的內存不超過系統可用內存的一半,這裏的除以3是爲了保證每個PoolArena至少可以由3個PoolChunk組成。
用戶如果想修改,則通過設置io.netty.allocator.numHeapArenas/numDirectArenas來進行修改。
6、對cache sizes進行了設置,如下:
DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.tinyCacheSize", 512);
DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.smallCacheSize", 256);
DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.normalCacheSize", 64);
7、 還對其他常量也進行了設置。
2、構造函數
下面來看下PooledByteBufAllocator的構造函數。
public PooledByteBufAllocator(boolean preferDirect) {
this(preferDirect, DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA, DEFAULT_PAGE_SIZE, DEFAULT_MAX_ORDER);
}
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder) {
this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder,
DEFAULT_TINY_CACHE_SIZE, DEFAULT_SMALL_CACHE_SIZE, DEFAULT_NORMAL_CACHE_SIZE);
}
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
int tinyCacheSize, int smallCacheSize, int normalCacheSize) {
super(preferDirect);
threadCache = new PoolThreadLocalCache();
this.tinyCacheSize = tinyCacheSize;
this.smallCacheSize = smallCacheSize;
this.normalCacheSize = normalCacheSize;
//得到chunkSize,其值爲:pageSize*2^maxOrder
final int chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
if (nHeapArena < 0) {
throw new IllegalArgumentException("nHeapArena: " + nHeapArena + " (expected: >= 0)");
}
if (nDirectArena < 0) {
throw new IllegalArgumentException("nDirectArea: " + nDirectArena + " (expected: >= 0)");
}
int pageShifts = validateAndCalculatePageShifts(pageSize);
if (nHeapArena > 0) {
heapArenas = newArenaArray(nHeapArena);
for (int i = 0; i < heapArenas.length; i ++) {
heapArenas[i] = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize);
}
} else {
heapArenas = null;
}
if (nDirectArena > 0) {
directArenas = newArenaArray(nDirectArena);
for (int i = 0; i < directArenas.length; i ++) {
directArenas[i] = new PoolArena.DirectArena(this, pageSize, maxOrder, pageShifts, chunkSize);
}
} else {
directArenas = null;
}
}
通過如上的構造函數可以看到,幹了如下幾件事:
1)使用了默認的值初始化了如下的字段:
private final int tinyCacheSize;
private final int smallCacheSize;
private final int normalCacheSize;
2)使用new PoolThreadLocalCache()實例化了threadCache 字段。
final PoolThreadLocalCache threadCache;
3)重點:實例化了如下兩個數組。
private final PoolArena<byte[]>[] heapArenas;
private final PoolArena<ByteBuffer>[] directArenas;
在實例化上面兩個PoolArena時,用到了如下的兩個參數
3.1)chunkSize
調用validateAndCalculateChunkSize函數求得,其值爲:pageSize*2^maxOrder
3.2)pageShifts
調用如下的validateAndCalculatePageShifts求得,
private static int validateAndCalculatePageShifts(int pageSize) {
if (pageSize < MIN_PAGE_SIZE) {
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + "+)");
}
if ((pageSize & pageSize - 1) != 0) {
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)");
}
// Logarithm base 2. At this point we know that pageSize is a power of two.
return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize);
}
該函數首先檢查了pageSize是否大於4K且爲2的冪次方,如果不是則拋異常。
如果是,則返回Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize)的結果,結果是什麼呢?
假設pageSize爲8192, 2的13次方的二進制碼爲:(0000 0000 0000 0000 0010 0000 0000 0000),其補碼與原碼一樣,而Integer.numberOfLeadingZeros返回pageSize的補碼最高位的1的左邊連續零的個數。而8192的二進制的高位有18個0,因此pageShifts爲13。簡單來說pageShifts=log(pageSize)。
既然看到這裏,PoolArena.HeapArena和PoolArena.DirectArena中如下的構造函數.
heapArenas[i] = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize);
directArenas[i] = new PoolArena.DirectArena(this, pageSize, maxOrder, pageShifts, chunkSize);
PoolArena.HeapArena構造函數如下;
HeapArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
super(parent, pageSize, maxOrder, pageShifts, chunkSize);
}
調用了父類PooledArena如下的構造函數
protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
//從PooledByteBufAllocator中傳送過來的相關字段值。
this.parent = parent;
this.pageSize = pageSize;
this.maxOrder = maxOrder;
this.pageShifts = pageShifts;
this.chunkSize = chunkSize;
subpageOverflowMask = ~(pageSize - 1);//該變量用於判斷申請的內存大小與page之間的關係,是大於,還是小於
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);
q075 = new PoolChunkList<T>(this, q100, 75, 100);
q050 = new PoolChunkList<T>(this, q075, 50, 100);
q025 = new PoolChunkList<T>(this, q050, 25, 75);
q000 = new PoolChunkList<T>(this, q025, 1, 50);
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25);
q100.prevList = q075;
q075.prevList = q050;
q050.prevList = q025;
q025.prevList = q000;
q000.prevList = null;
qInit.prevList = qInit;
}
該構造函數主要乾了如下幾件事
1)初始化parent、pageSize、maxOrder、pageShifts等字段
2)實例化了如下兩個數組,這兩個數組相當重要,在博文Netty源碼分析:PoolArena中有詳細的介紹,這裏不再介紹。
private final PoolSubpage<T>[] tinySubpagePools;
private final PoolSubpage<T>[] smallSubpagePools;
3)創建了6個Chunk列表(PoolChunkList)來緩存用來分配給Normal(超過一頁)大小內存的PoolChunk,每個PoolChunkList中用head字段維護一個PoolChunk鏈表的頭部,每個PoolChunk中有prev,next字段。而PoolChunkList內部維護者一個PoolChunk鏈表頭部。
這6個PoolChunkList解釋如下:
qInit:存儲剩餘內存0-25%的chunk
q000:存儲剩餘內存1-50%的chunk
q025:存儲剩餘內存25-75%的chunk
q050:存儲剩餘內存50-100%個chunk
q075:存儲剩餘內存75-100%個chunk
q100:存儲剩餘內存100%chunk
這六個PoolChunkList也通過鏈表串聯,串聯關係是:qInit->q000->q025->q050->q075->q100.
3、ByteBuf byteBuf = allocator.heapBuffer(256)
接下來看下PooledByteBufAllocator類中heapBuffer方法(實際上是在其父類AbstractByteBufAllocator中定義的),代碼如下:
@Override
public ByteBuf heapBuffer(int initialCapacity) {
return heapBuffer(initialCapacity, Integer.MAX_VALUE);
}
@Override
public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
if (initialCapacity == 0 && maxCapacity == 0) {
return emptyBuf;
}
validate(initialCapacity, maxCapacity);//檢查參數是否正確
return newHeapBuffer(initialCapacity, maxCapacity);
}
繼續看newHeapBuffer方法
PooledByteBufAllocator
@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
PoolThreadCache cache = threadCache.get();
PoolArena<byte[]> heapArena = cache.heapArena;
ByteBuf buf;
if (heapArena != null) {
buf = heapArena.allocate(cache, initialCapacity, maxCapacity);//分析
} else {
buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
return toLeakAwareBuffer(buf);
}
從上面的方法可知,接着就時調用了PoolArena的子類的allocate來分配內存,這個方法在博文Netty源碼分析:PoolArena有詳細的介紹,這裏不再介紹。
小結
基於前面分析的PoolArena、PoolChunk、PoolSubage這個類之後,他們的關係總結如下:
圖中想表達以下幾點:
1、PooledByteBufAllocator包括2個數組:HeapArena數組和DirectArena數組。當利用PooledByteBufAllocator分配內存時,是利用Arena數組中的元素來完成。
2、HeapArena包括:6個PoolChunkList鏈表(鏈表中的元素爲PoolChunk),和兩個數組:tinySubpagePools和smallSubpagePools。當利用Arena來進行分配內存時,根據申請內存的大小有不同的策略,例如:如果申請內存的大小小於512時,則首先在cache嘗試分配,如果分配不成功則會在tinySubpagePools嘗試分配,如果分配不成功,則會在PoolChunk重新找一個PoolSubpage來進行內存分配,分配之後將此PoolSubpage保存到tinySubpagePools中。
3、PoolChunk中包括一大塊內存T memory,將其分成N份,每一份就是一個PoolSubpage。
4、PoolSubpage由M個“塊”構成,塊的大小由第一次申請內存大小決定。當分配一次內存之後此page會被加入到PoolArena的tinySubpagePools或smallSubpagePools中,下次分配時就如果“塊”大小相同,則尤其直接分配。