在本文中,我们将深入探讨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/n/374651.html