netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放
ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机。
内存分配分两个阶段: 第一阶段,初始化时分配内存。第二阶段: 内存不够用时分配新的内存。ByteBuf抽象层没有定义第一阶段的行为,但定义了第二阶段的方法:
public abstract ByteBuf capacity(int newCapacity)
这个方法负责分配一个长度为newCapacity的新内存。
内存释放的抽象实现在AbstractReferenceCountedByteBuf中实现,这个类实现引用计数,当调用release方法把引用计数变成0时,会调用
protected abstract void deallocate()
执行真正的内存释放操作。
内存相关的属性
ByteBuf定义了两个内存相关的属性:
capacity: 当前的当前的容量,也就是当前使用的内存大小。使用capacity()方法获得。
maxCapacity: 最大容量,也就是可以使用的最大内存大小。使用newCapacity()方法获得。
内存分配时机
AbstractByteBuf定义了内存分配的时机。当writeXX方法被调用的时候,如果如果发现可写空间不足,就调用capacity分配新的内存。下面以writeInt为例详细分析这个过程。
@Override
public ByteBuf writeInt(int value) {
ensureWritable0(4);
_setInt(writerIndex, value);
writerIndex += 4;
return this;
} final void ensureWritable0(int minWritableBytes) {
ensureAccessible();
if (minWritableBytes <= writableBytes()) {
return;
} if (minWritableBytes > maxCapacity - writerIndex) {
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
} // Normalize the current capacity to the power of 2.
int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes); // Adjust to the new capacity.
capacity(newCapacity);
}
3行: ensureWritable确保当前能写入4Byte的数据。
11行: 确保当前ByteBuf是可以访问的。防止多线程环境下,ByteBuf内存被释放后读写数据。
12,13行: 如果内存够用,就此作罢。
16行: 如果需要内存大于maxCapacity抛出异常。
23, 26行行: 计算新内存的大小, 调用capacity(int)分配新内存。
重新分配内存之前的一个重要步骤的计算新内存的大小。这个工作由calculateNewCapacity方法完成,它的代码如下:
private int calculateNewCapacity(int minNewCapacity) {
final int maxCapacity = this.maxCapacity;
final int threshold = 1048576 * 4; // 4 MiB page
if (minNewCapacity == threshold) {
return threshold;
}
// If over threshold, do not double but just increase by threshold.
if (minNewCapacity > threshold) {
int newCapacity = minNewCapacity / threshold * threshold;
if (newCapacity > maxCapacity - threshold) {
newCapacity = maxCapacity;
} else {
newCapacity += threshold;
}
return newCapacity;
}
// Not over threshold. Double up to 4 MiB, starting from 64.
int newCapacity = 64;
while (newCapacity < minNewCapacity) {
newCapacity <<= 1;
}
return Math.min(newCapacity, maxCapacity);
}
1行:接受一个最小的新内存参数minNewCapacity。
3行: 定义一个4MB的阈值常量threshold。
5,6行: 如果minNewCapacity==threshold,那么新内存大小就是threshold。
10-17行: 如果minNewCapacity>threshold, 新内存大小是min(maxCapacity, threshold * n)且threshold * n >= minNewCapacity。
21-26行: 如果minNewCapacity<threshold, 新内存大小是min(maxCapacity, 64 * 2n)且64 * 2n >= minNewCapacity。
内存分配和释放的具体实现
本章涉及到的内存分配和释放的具体实现只涉及到unpooled类型的ByteBuf,即:
UnpooledHeapByteBuf
UnpooledDirectByteBuf
UnpooledUnsafeHeapByteBuf
UnpooledUnsafeDirectByteBuf
这几个具体实现中涉及到的内存分配和释放代码比较简洁,更容易明白ByteBuf内存管理的原理。相比之下,pooled类型的ByteBuf内存分配和释放的代码要复杂很多,会在后面的章节独立分析。
UnpooledHeapByteBuf和UnpooledUnsafeHeapByteBuf实现
UnpooledHeapByteBuf中,内存分配的实现代码主要集中在capacity(int)和allocateArray()方法中。capacity分配新内存的步骤是
- 调用allocateArray分配一块新内存。
- 把旧内存中的实际复制到新内存中。
- 使用新内存替换旧内存(24行)。
- 释放掉旧内存(25行)。
代码如下:
@Override
public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity); int oldCapacity = array.length;
byte[] oldArray = array;
if (newCapacity > oldCapacity) {
byte[] newArray = allocateArray(newCapacity);
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
setArray(newArray);
freeArray(oldArray);
} else if (newCapacity < oldCapacity) {
byte[] newArray = allocateArray(newCapacity);
int readerIndex = readerIndex();
if (readerIndex < newCapacity) {
int writerIndex = writerIndex();
if (writerIndex > newCapacity) {
writerIndex(writerIndex = newCapacity);
}
System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
} else {
setIndex(newCapacity, newCapacity);
}
setArray(newArray);
freeArray(oldArray);
}
return this;
}
capacity中复制旧内存数据到新内存中的时候分两种情况(newCapacity,oldCapacity分别是新旧内存的大小):
- newCapacity>oldCapacity,这种情况比较简单,直接复制就好了(第8行)。不影响readerIndex和writerIndex。
- newCapacity<oldCapacity, 这种情况比较复杂。capacity尽量把可读的数据复制新内存中。
- 如果readerIndex<newCapacity且writerIndex<newCapacity。可读数据会全部转移到新内存中。readerIndex和writerIndex保持不变。
- 如果readerIndex<newCapacity且writeIndex>newCapacity。可端数据会部分转移的新内存中,会丢失部分可读数据。readerIndex不变,writerIndex变成newCapacity。
- 如果readerIndex>newCapacity,数据全部丢失,readerIndex和writerIndex都会变成newCapacity。
allocateArray方法负责分配一块新内存,它的实现是new byte[]。freeArray方法负责释放内存,这个方法是个空方法。
UnpooledUnsafeHeapByteBuf是UnloopedHeadpByteBuf的直接子类,在内存管理上的差别是allocateArray的实现,UnpooledUnsafeHeapByteBuf的实现是:
@Override
byte[] allocateArray(int initialCapacity) {
return PlatformDependent.allocateUninitializedArray(initialCapacity);
}
UnpooledDirectByteBuf和UnpooledUnsafeDirectByteBuf实现
UnpooledDirectByteBuf类使用DirectByteBuffer作为内存,使用了DirectByteBuffer的能力来实现ByteBuf接口。allocateDirect和freeDirect方法负责分配和释放DirectByteBuffer。capacity(int)方法和UnloopedHeapByteBuf类似,使用allocateDirect创建一个新的DirectByteBuffer, 把旧内存数据复制到新内存中,然后使用新内存替换旧内存,最后调用freeDirect方法释放掉旧的DirectByteBuffer。
protected ByteBuffer allocateDirect(int initialCapacity) {
return ByteBuffer.allocateDirect(initialCapacity);
}
protected void freeDirect(ByteBuffer buffer) {
PlatformDependent.freeDirectBuffer(buffer);
}
@Override
public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity);
int readerIndex = readerIndex();
int writerIndex = writerIndex();
int oldCapacity = capacity;
if (newCapacity > oldCapacity) {
ByteBuffer oldBuffer = buffer;
ByteBuffer newBuffer = allocateDirect(newCapacity);
oldBuffer.position(0).limit(oldBuffer.capacity());
newBuffer.position(0).limit(oldBuffer.capacity());
newBuffer.put(oldBuffer);
newBuffer.clear();
setByteBuffer(newBuffer);
} else if (newCapacity < oldCapacity) {
ByteBuffer oldBuffer = buffer;
ByteBuffer newBuffer = allocateDirect(newCapacity);
if (readerIndex < newCapacity) {
if (writerIndex > newCapacity) {
writerIndex(writerIndex = newCapacity);
}
oldBuffer.position(readerIndex).limit(writerIndex);
newBuffer.position(readerIndex).limit(writerIndex);
newBuffer.put(oldBuffer);
newBuffer.clear();
} else {
setIndex(newCapacity, newCapacity);
}
setByteBuffer(newBuffer);
}
return this;
}
对比UnloopedHeapByteBuf的capacity(int)方法,发现这两个实现非常类似,也分两种情况处理:
- 18-24行,newCapacity > oldCapacity的情况。
- 26-39行, newCapacity < oldCapacity的情况。
两种情况对readerIndex和writerIndex的影响也一样,不同的是数据复制时的具体实现。
UnpooledUnsafeDirectByteBuf和UnpooledDirectByteBuf同属于AbstractReferenceCountedByteBuf的派生类,它们之间没有继承关系。但内存分配和释放实现是一样的,不同的地方是内存I/O。UnpooledUnsafeDirectByteBuf使用UnsafeByteBufUtil类之间读写DirectByteBuffer的内存,没有使用DirectByteBuffer的I/O能力。
netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放的更多相关文章
- netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
PoolArena实现了用于高效分配和释放内存,并尽可能减少内存碎片的内存池,这个内存管理实现使用PageRun/PoolSubpage算法.分析代码之前,先熟悉一些重要的概念: page: 页,一个 ...
- netty源码解解析(4.0)-24 ByteBuf基于内存池的内存管理
io.netty.buffer.PooledByteBuf<T>使用内存池中的一块内存作为自己的数据内存,这个块内存是PoolChunk<T>的一部分.PooledByteBu ...
- netty源码解解析(4.0)-22 ByteBuf的I/O
ByteBuf的I/O主要解决的问题有两个: 管理readerIndex和writerIndex.这个在在AbstractByteBuf中解决. 从内存中读写数据.ByteBuf的不同实现主要 ...
- netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架
编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...
- netty源码解解析(4.0)-11 Channel NIO实现-概览
结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...
- netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理
事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...
- netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现
io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...
- netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端
本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...
- netty源码解解析(4.0)-15 Channel NIO实现:写数据
写数据是NIO Channel实现的另一个比较复杂的功能.每一个channel都有一个outboundBuffer,这是一个输出缓冲区.当调用channel的write方法写数据时,这个数据被一系列C ...
随机推荐
- HDU-DuoXiao第二场hdu 6315 Naive Operations 线段树
hdu 6315 题意:对于一个数列a,初始为0,每个a[ i ]对应一个b[i],只有在这个数字上加了b[i]次后,a[i]才会+1. 有q次操作,一种是个区间加1,一种是查询a的区间和. 思路:线 ...
- POJ 1390 Blocks (区间DP) 题解
题意 t组数据,每组数据有n个方块,给出它们的颜色,每次消去的得分为相同颜色块个数的平方(要求连续),求最大得分. 首先看到这题我们发现我们要把大块尽可能放在一起才会有最大收益,我们要将相同颜色块合在 ...
- ~!#$%^&*这些符号怎么读? 当然是用英语(键盘特殊符号小结)
~!#$%^&*这些符号怎么读? 当然是用英语(键盘特殊符号小结) 感谢原文作者:http://www.360doc.com/content/14/0105/20/85007_342874 ...
- 脱离脚手架来配置、学习 webpack4.x (一)基础搭建项目
序 现在依旧记得第一次看到webpack3.x 版本配置时候的状态 刚开始看到这些真的是一脸懵.希望这篇文章能帮到刚开始入门的同学. webpack 是什么? webpack是一个模块化打包工具,w ...
- NetCore下的HTTP请求IHttpClientFactory
使用方式 IHttpClientFactory有四种模式: 基本用法 命名客户端 类型化客户端 生成的客户端 基本用法 在 Startup.ConfigureServices 方法中,通过在 ISer ...
- IDEA中全局搜索不起作用,解决办法
众所周知IDEA中全局搜索的快捷键是Ctrl+Shift+F,但是今天却碰到了用不了的情况,其实软件坏了的可能性很小,那就要从外部再来找原因,查看自己开的软件,一一查看快捷键,看是否是快捷键冲突: 1 ...
- 通过网上的webservice自己编写两个客户端
1.根据电话号码查询归属地等信息 根据http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl采用jdk生成所需的代码,编写一个contro ...
- 前端利器躬行记(5)——Git
Git是一款开源的分布式版本控制系统,它的出现和Linux紧密相关.Linux内核项目组为了能更好地管理和维护Linux内核开发,于2002年开始启用商业的分布式版本控制系统BitKeeper.虽然软 ...
- WTM送书活动:向更遥远的星辰大海起航~
是的,没错~ 这一篇不是大老刘写的.哈哈~ 啥? 你想知道为啥? 大老刘为了你们不加班,熬夜改BUG,姑娘不乐意了... 然后... 后面请自行脑补~ 哎~生活还要继续鸭.... 那么,接下来由我陪 ...
- Flutter 中文文档网站 flutter.cn 正式发布!
在通常的对 Flutter 介绍中,最耳熟能详的是下面四个特点: 精美 (Beautiful):充分的赋予和发挥设计师的创造力和想象力,让你真正掌控屏幕上的每一个像素. ** 极速 (Fast)**: ...