Netty 内存管理

内存管理

Netty的内存管理有两部分要点:自适应内存大小算法和真实的内存管理:

再讲之前我们再来看看数据读取的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public final void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
// 用来分配ByteBuf内存的 PooledByteBufAllocator
final ByteBufAllocator allocator = config.getAllocator();
// 用来决定分配多大的 ByteBuf (自适应缓存分配大小),以防止缓存分配过多或过少
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
do {
// allocator.ioBuffer(allocHandle.guess());
byteBuf = allocHandle.allocate(allocator);
// 在读取的过程中,alloHandle会记录读取了多少字节
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
}

allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
// 跟据本次读取的字节,计算下一次应该分配的ByteBuf大小
allocHandle.readComplete();
pipeline.fireChannelReadComplete();

if (close) {
closeOnRead(pipeline);
}
}

可以看到两个在内存分配管理上比较重要的AllocatorAllocHandle

Allocator 是用来分配内存的,而AllocHandle则是用来辅助分配内存的,决定在不知道内存空间有多大的情况下预分配多大的ByteBuf

AllocHandle(决定分配ByteBuf大小)

AllocHandle,其实现有多种。这里,重点来看AdaptiveRecvByteBufAllocator.HandleImpl

AdaptiveRecvByteBufAllocator内部会维护以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 初始化时的配置
static final int DEFAULT_MINIMUM = 64;
static final int DEFAULT_INITIAL = 1024;
static final int DEFAULT_MAXIMUM = 65536;

// index的自增值
private static final int INDEX_INCREMENT = 4;
// index自建值
private static final int INDEX_DECREMENT = 1;

// SIZE_TABLE 用来存放分配大小的尺度
private static final int[] SIZE_TABLE;

// 根据最小配置大小得到的在SIZE_TABLE中的下标
private final int minIndex;
private final int maxIndex;
private final int initial;

static {
List<Integer> sizeTable = new ArrayList<Integer>();
for (int i = 16; i < 512; i += 16) {
sizeTable.add(i);
}
for (int i = 512; i > 0; i <<= 1) {
sizeTable.add(i);
}
SIZE_TABLE = new int[sizeTable.size()];
for (int i = 0; i < SIZE_TABLE.length; i ++) {
SIZE_TABLE[i] = sizeTable.get(i);
}
// 初始化完成后,sizeTable : 16, 32, 48, 64, ..., 496,512,1024,2048...
}

在读取数据的时候,alloHandle会记录一次读取总共读取了多少字节(lastBytesRead),完成读取后会调用readComplete会计算下次应该分配多大内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void readComplete() {
record(totalBytesRead());
}
private void record(int actualReadBytes) {
// 是否比(当前缩容后)的大小还要小
if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {
if (decreaseNow) {
index = Math.max(index - INDEX_DECREMENT, minIndex);
nextReceiveBufferSize = SIZE_TABLE[index];
decreaseNow = false;
} else {
decreaseNow = true;
}
// 是否比当前扩容后的大小还要大
} else if (actualReadBytes >= nextReceiveBufferSize) {
index = Math.min(index + INDEX_INCREMENT, maxIndex);
nextReceiveBufferSize = SIZE_TABLE[index];
decreaseNow = false;
}
}

当决定分配内存大小的时候调用的是guess方法,可以看出正式上一次计算出的大小

1
2
3
public int guess() {
return nextReceiveBufferSize;
}

Allocator 内存分配

Allocator的内存分配实现有以下两个:

  • PooledByteBufAllocator:基于内存池的字节缓冲区分配器
  • UnpooledByteBufAllocator:普通的字节缓存区分配器

##TODO

您的支持是我创作源源不断的动力