- 1 概述
- 2 重要静态变量
- 3 静态初始化语句块的参数初始化
1 概述
众所周知,采用直接内存(off-heap)可以避免内存拷贝,因此在NIO编程中大多会采用直接内存,但是直接内存的分配和回收代价较高,Netty为了提高内存分配和回收的效率,采用了内存池机制,也即预先分配一大块内存,然后采用自己实现的内存分配算法在预先分配的内存块中进行内存空间分配和回收。当然Netty不仅对直接内存采用了内存池技术,对堆内存也使用了内存池技术。
PooledByteBufAllocator
是Netty中的默认ByteBuf
分配器,负责分配所需的ByteBuf
,从其名字可知PooledByteBufAllocator
是采用内存池进行内存分配的。但是并不是由PooledByteBufAllocator
分配的ByteBuf
都是采用内存池方式分配的,在PooledByteBufAllocator
的静态初始化语句块会进行内存、配置等的检查,如果有不通过项或者达不到系统预设条件,则还是会跳过内存池技术进行堆或直接内存的直接分配。
2 重要静态变量
PooledByteBufAllocator
中的重要静态变量如下:
//PooledByteBufAllocator
//首先要有个总体上的概念,不管是直接内存还是堆内存,
//都是由多个Page组成Chunk,每个Chunk对应一个直接内存ByteBuffer
//(注意这里是Java原生ByteBuffer)或者内存数组,多个Chunk则组成
//一个Arena,内存分配是在Arena上进行分配的,分配时会具体从Arena的
//某个Chunk中分配满足大小的页
//默认的堆内内存Arena个数
private static final int DEFAULT_NUM_HEAP_ARENA;
//默认的堆外内存(直接内存)Arena个数
private static final int DEFAULT_NUM_DIRECT_ARENA;
//默认的Page(页)字节数
private static final int DEFAULT_PAGE_SIZE;
//order决定了chunk的大小,chunk大小等于page大小<
3 静态初始化语句块的参数初始化
//从配置参数io.netty.allocator.pageSize获取用户
//指定的page大小,如果没有配置则为8192,即8k
int defaultPageSize = SystemPropertyUtil.getInt("io.netty.allocator.pageSize", 8192);
Throwable pageSizeFallbackCause = null;
try {
//这里主要检查指定的page大小要是2的指数方并且大于
//上面介绍的page大小的最小值MIN_PAGE_SIZE,即最小4k
validateAndCalculatePageShifts(defaultPageSize);
} catch (Throwable t) {
//如果不满足上面的验证,则page大小为默认值8k
pageSizeFallbackCause = t;
defaultPageSize = 8192;
}
DEFAULT_PAGE_SIZE = defaultPageSize;
private static int validateAndCalculatePageShifts(int pageSize) {
//page大小要小于MIN_PAGE_SIZE,4k
if (pageSize < MIN_PAGE_SIZE) {
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + ")");
}
//必须为2的指数
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);
}
//上面介绍了chunk大小等于page大小< 14) {
throw new IllegalArgumentException("maxOrder: " + maxOrder + " (expected: 0-14)");
}
//保证page << order,不大于最大的chunk大小,MAX_CHUNK_SIZE
// Ensure the resulting chunkSize does not overflow.
int chunkSize = pageSize;
for (int i = maxOrder; i > 0; i --) {
if (chunkSize > MAX_CHUNK_SIZE / 2) {
throw new IllegalArgumentException(String.format(
"pageSize (%d) << maxOrder (%d) must not exceed %d", pageSize, maxOrder, MAX_CHUNK_SIZE));
}
chunkSize <<= 1;
}
return chunkSize;
}
//默认最小arena数量Wie处理器个数*2
final int defaultMinNumArena = NettyRuntime.availableProcessors() * 2;
//默认的chunk大小为page << order
final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
//从参数io.netty.allocator.numHeapArenas读取用户指定的堆arena
//个数,如果没有指定,则取上面defaultMinNumArena和
//runtime.maxMemory() / defaultChunkSize / 2 / 3二者较小值
//这里除以2是为了保证【堆内内存池】arena大小不能超过可用【堆内存】
//的一半,除以3是为了保证每个arena中至少有三个chunk
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numHeapArenas",
(int) Math.min(
defaultMinNumArena,
runtime.maxMemory() / defaultChunkSize / 2 / 3)));
//和上面同理
//但是这里保证的【直接内存池】arena不超过配置的【最大直接内存】的一半
DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numDirectArenas",
(int) Math.min(
defaultMinNumArena,
PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));
//如果按照page=8k, order=11,则chunk = 8k << 11 = 16M
//那么16 * 2 * 3 = 96M,所以默认情况下最少配置96M的堆内存或者直接
//内存才会启用内存池
静态初始化语句块还有决定上面各种Cache大小的语句,本文不介绍。
//Netty启动之后,可以关注下面log输出,可以知道内存池的启用情况
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.allocator.numHeapArenas: {}", DEFAULT_NUM_HEAP_ARENA);
logger.debug("-Dio.netty.allocator.numDirectArenas: {}", DEFAULT_NUM_DIRECT_ARENA);
if (pageSizeFallbackCause == null) {
logger.debug("-Dio.netty.allocator.pageSize: {}", DEFAULT_PAGE_SIZE);
} else {
logger.debug("-Dio.netty.allocator.pageSize: {}", DEFAULT_PAGE_SIZE, pageSizeFallbackCause);
}
if (maxOrderFallbackCause == null) {
logger.debug("-Dio.netty.allocator.maxOrder: {}", DEFAULT_MAX_ORDER);
} else {
logger.debug("-Dio.netty.allocator.maxOrder: {}", DEFAULT_MAX_ORDER, maxOrderFallbackCause);
}
logger.debug("-Dio.netty.allocator.chunkSize: {}", DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER);
logger.debug("-Dio.netty.allocator.tinyCacheSize: {}", DEFAULT_TINY_CACHE_SIZE);
logger.debug("-Dio.netty.allocator.smallCacheSize: {}", DEFAULT_SMALL_CACHE_SIZE);
logger.debug("-Dio.netty.allocator.normalCacheSize: {}", DEFAULT_NORMAL_CACHE_SIZE);
logger.debug("-Dio.netty.allocator.maxCachedBufferCapacity: {}", DEFAULT_MAX_CACHED_BUFFER_CAPACITY);
logger.debug("-Dio.netty.allocator.cacheTrimInterval: {}", DEFAULT_CACHE_TRIM_INTERVAL);
logger.debug("-Dio.netty.allocator.useCacheForAllThreads: {}", DEFAULT_USE_CACHE_FOR_ALL_THREADS);
}
上面省略未介绍的DEFAULT_TINY_CACHE_SIZE
等变量和PoolThreadCache
有关,放在笔者文章Netty源码-PoolThreadCache中介绍。