io.netty.buffer.PooledByteBuf<T>使用内存池中的一块内存作为自己的数据内存,这个块内存是PoolChunk<T>的一部分。PooledByteBuf<T>是一个抽象类型,它有4个派生类:

  • PooledHeapByteBuf, PooledUnsafeHeapByteBuf 使用堆内存的PooledByteBuffer<byte[]>。
  • PooledDirectByteBuf, PooledUnsafeDirectByteBuf 使用直接内存的PooledByteBuf<ByteBuffer>。

初始化

  PooledByteBuf的初始化过程分为两个步骤:创建实例;初始化内存。这两个步骤的代码如下:

    protected PooledByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
super(maxCapacity);
this.recyclerHandle = recyclerHandle;
} void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
init0(chunk, handle, offset, length, maxLength, cache);
} private void init0(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
assert handle >= 0;
assert chunk != null; this.chunk = chunk;
memory = chunk.memory;
allocator = chunk.arena.parent;
this.cache = cache;
this.handle = handle;
this.offset = offset;
this.length = length;
this.maxLength = maxLength;
tmpNioBuf = null;
}

  创建实例时调用的构造方法只是为maxCapacity和recyclerHandler属性赋值,构造方法是protected,不打算暴露到外面。派生类都提供了newInstance方法创建实例,以PooledHeapByteBuf为例,它的newInstance方法实现如下:

     private static final Recycler<PooledHeapByteBuf> RECYCLER = new Recycler<PooledHeapByteBuf>() {
@Override
protected PooledHeapByteBuf newObject(Handle handle) {
return new PooledHeapByteBuf(handle, 0);
}
}; static PooledHeapByteBuf newInstance(int maxCapacity) {
PooledHeapByteBuf buf = RECYCLER.get();
buf.reuse(maxCapacity);
return buf;
}

  这里的newInstance使用RECYCLER创建实例对象。Recycler<T>是一个轻量级的,支持循环使用的对象池。当对象池中没有可用对象时,会在第4行使用构造方法创建一个新的对象。

  init调用init0初始化数据内存,init0方法为几个内存相关的关键属性赋值:

  • chunk:  PoolChunk<T>对象,这个PooledByteBuf使用的内存就是它的一部分。
  • memory: 内存对象。更准确地说,PooledByteBuf使用的内存是它的一部分。
  • allocator: 创建这个PooledByteBuf的PooledByteBufAllocator对象。
  • cache:  线程专用的内存缓存。分配内存时会优先从这个缓存中寻找合适的内存块。
  • handle:  内存在chunk中node的句柄。chunk使用handle可以计算出它对应内存的起始位置offset。
  • offset:  分配内存的起始位置。
  • length: 分配内存的长度,也是这个PooledByteBuf的capacity。
  • maxLength: 这块内存node的最大长度。当调用capacity(int newCapacity)方法增加capacity时,只要newCapacity不大于这个值,就不用从新分配内存。

  内存初始化完成之后,这个PooledByteBuf可使用的内存范围是memory内存中[offset, offset+length)。idx方法可以把ByteBuf的索引转换成memory的索引:

     protected final int idx(int index) {
return offset + index;
}

重新分配内存

  和前面讲过的ByteBuf实现一样,PooledByteBuf也需要使用capacity(int newCapacity)改变内存大小,也会涉及到把数据从旧内存中复制到新内存的问题。也就是说,要解决的问题是一样的,只是具体实现的差异。

     @Override
public final ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity); // If the request capacity does not require reallocation, just update the length of the memory.
if (chunk.unpooled) {
if (newCapacity == length) {
return this;
}
} else {
if (newCapacity > length) {
if (newCapacity <= maxLength) {
length = newCapacity;
return this;
}
} else if (newCapacity < length) {
if (newCapacity > maxLength >>> 1) {
if (maxLength <= 512) {
if (newCapacity > maxLength - 16) {
length = newCapacity;
setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
return this;
}
} else { // > 512 (i.e. >= 1024)
length = newCapacity;
setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
return this;
}
}
} else {
return this;
}
} // Reallocation required.
chunk.arena.reallocate(this, newCapacity, true);
return this;
}

  这个方法处理两大类情况: 不重新分配内存;重新分配内存并复制ByteBuf中的数据和状态。

  不重新分配内存: 

  8行: chunk不需要回收到内存池中,且newCapacity没有变化。

  11-32行: chunk需要回收到内存池中。

    13-14行:内存增大,且newcapacity不大于maxLength。把容量修改成newCapacity即可。

    20-22行: 内存减小,  newCapacity 大于maxLength的一半,maxLength<=512, newCapacity >maxLength - 16。 把容量修改成newCapacity, 调整readerIndex, writerIndex。

    25-27行: 内存减小,newCapacity大于maxLength的一半,  maxLength > 512。把容量修改成newCapacity, 调整readerIndex, writerIndex。

    31行: 内存不变,不做任何操作。

  需要重新分配内存:

  36行: 任何不满足以上情况的都要重新分配内存。这里使用Arena的reallocate方法重新分配内存,并把旧内存释放调,代码如下:

     //io.netty.buffer.PoolArena#reallocate,
void reallocate(PooledByteBuf<T> buf, int newCapacity, boolean freeOldMemory) {
if (newCapacity < 0 || newCapacity > buf.maxCapacity()) {
throw new IllegalArgumentException("newCapacity: " + newCapacity);
} int oldCapacity = buf.length;
if (oldCapacity == newCapacity) {
return;
} PoolChunk<T> oldChunk = buf.chunk;
long oldHandle = buf.handle;
T oldMemory = buf.memory;
int oldOffset = buf.offset;
int oldMaxLength = buf.maxLength;
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex(); allocate(parent.threadCache(), buf, newCapacity);
if (newCapacity > oldCapacity) {
memoryCopy(
oldMemory, oldOffset,
buf.memory, buf.offset, oldCapacity);
} else if (newCapacity < oldCapacity) {
if (readerIndex < newCapacity) {
if (writerIndex > newCapacity) {
writerIndex = newCapacity;
}
memoryCopy(
oldMemory, oldOffset + readerIndex,
buf.memory, buf.offset + readerIndex, writerIndex - readerIndex);
} else {
readerIndex = writerIndex = newCapacity;
}
} buf.setIndex(readerIndex, writerIndex); if (freeOldMemory) {
free(oldChunk, oldHandle, oldMaxLength, buf.cache);
}
}

  7-9行: 内存大小没变化,返回。

  12-18行: 记录下旧内存的信息,readerIndex, writerIndex。

  20行: 为PooledByteBuf分配新内存。

  21-38行: 把旧内存中数据复制到新内存,并把readerIndex,writerIndex设置在正确。

  41行: 释放就内存。

释放内存

  内存释放代码在deallocate中:

     @Override
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
tmpNioBuf = null;
chunk.arena.free(chunk, handle, maxLength, cache);
chunk = null;
recycle();
}
}

  关键是第8行代码,使用PoolArena的free方法释放内存。然后是recycle把当前PooledByteBuf对象放到RECYCLER中循环使用。

PooledByteBufAllocator创建内存管理模块

  在前面分析PooledByteBuf内存初始化,重新分配及释放时,看到了内存管理的三个核心模块: PoolArena(chunk.arena),  PoolChunk(chunk),  PoolThreadCache(cache)。PooledByteBuf的内存管理能力都是使用这三模块实现的,它本身没有实现内存管理算法。当需要为PooledByteBuf分配一块内存时,先从一个线程专用的PoolThreadCache中得到一个PoolArena,  使用PoolArena的allocate找到一个满足要求内存块PoolChunk,  从这个内存块中分配一块连续的内存handle,计算出这块内存起始位置的偏移量offset, 最后调用PooledByteBuf的init方法初始化内存完成内存分配。 释放内存调用PoolArena的free方法。在内存分配时,会优先从PoolThreadCache中寻找合适的内存块。在内存释放时会把内存块暂时放在PoolThreadCache中,等使用频率过低时才会还给PoolChunk。这三个模块中PoolArena,  PoolThreadCache由PooledByteBufAllocator创建,PoolChunk由PoolArean维护。

  PooledByteBufAllocator维护了相关的几个属性:

  PoolArena<byte[]>[] heapArenas

  PoolArena<ByteBuffer>[] directArenas

  PoolThreadLocalCache threadCache

  headArenas和directArenas分别维护了多个PoolArena, 他们分别用来分配堆内存和直接内存。 如果使用得当,可以让每个线程持有一个专用的PoolArena,  避免线程间数据同步的开销。PoolThreadLocalCache会为每个线程创建一个专用的PoolThreadCache实例,这个实例分别持有一个heapArena和directArena。

  接下来的的几个章节会详细分析PoolArena和PoolThreadCache的实现代码。

netty源码解解析(4.0)-24 ByteBuf基于内存池的内存管理的更多相关文章

  1. netty源码解解析(4.0)-22 ByteBuf的I/O

        ByteBuf的I/O主要解决的问题有两个: 管理readerIndex和writerIndex.这个在在AbstractByteBuf中解决. 从内存中读写数据.ByteBuf的不同实现主要 ...

  2. netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放

    ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机. 内存分配分两个阶段: 第一阶段,初始化时分配内存.第二阶段: 内存不够用时分配新的内存.ByteBuf抽象层没有定义 ...

  3. netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk

    PoolArena实现了用于高效分配和释放内存,并尽可能减少内存碎片的内存池,这个内存管理实现使用PageRun/PoolSubpage算法.分析代码之前,先熟悉一些重要的概念: page: 页,一个 ...

  4. netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现

    io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...

  5. netty源码解解析(4.0)-11 Channel NIO实现-概览

      结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...

  6. netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理

    事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...

  7. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  8. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

  9. netty源码解解析(4.0)-12 Channel NIO实现:channel初始化

    创建一个channel实例,并把它register到eventLoopGroup中之后,这个channel然后处于inactive状态,仍然是不可用的.只有在bind或connect方法调用成功之后才 ...

随机推荐

  1. HTML5 01. 布局、语义化标签、智能化表单、表单元素/标签/属性/事件、多媒体、类操作、自定义属性

    1.知识点 lang = “en”   所用语言是英文 文档结构更简洁 IE8一下不支持h5c3 书写更宽松 div没有语义 标签语义化:在合适的地方使用合适的标签 对seo优化友谊 网页经典布局 页 ...

  2. 【Offer】[56-1] 【数组中只出现一次的两个数字】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 一个整型数组里除两个数字之外,其他数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是0(1). ...

  3. NameNode数据存储

    HDFS架构图 HDFS原理 1)  三大组件 NameNode. DataNode .SecondaryNameNode 2)NameNode 存储元数据(文件名.创建时间.大小.权限.文件与blo ...

  4. Cannot create PoolableConnectionFactory (null, message from server: "Host 'admin-PC' is not allowed to connect to this MySQL server")

    1.别人在用自己的tomcat访问我留的查询接口时,出现Cannot create PoolableConnectionFactory (null,  message from server: &qu ...

  5. 纯css实现乌云密布的天气图标

    效果 效果如下 ​ 实现思路 使用box-shadow属性画几个灰色的圆,将这些圆错落的组合在一起,形成乌云图案 after伪元素写乌云下的投影 增加动画 dom结构 用两个嵌套的div容器,父容器来 ...

  6. 各种浏览器UA值

    UA  User-Agent:用户代理,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器引擎.浏览器语言.浏览器插件等. 标准格式为: 浏览器标识 ...

  7. Prometheus Operator 监控Kubernetes

    Prometheus Operator 监控Kubernetes 1. Prometheus的基本架构 ​ Prometheus是一个开源的完整监控解决方案,涵盖数据采集.查询.告警.展示整个监控流程 ...

  8. 44 (OC)* 野指针、空指针

    一: 空指针 : 没有存储任何内存地址的指针就称为空指针(NULL指针). 被赋值为nil的指针,在没有被具体初始化之前,为nil.nil.Nil.NULL.NSNULL的含义和区别 nil:OC中的 ...

  9. docker安装centos6

    1,获取Centos镜像>docker pull centos:centos6 2,查看镜像运行情况>docker images centos 3,在容器下运行 shell bash> ...

  10. C#控件及常用属性

    1.窗体(Form) 1.常用属性 (1)Name 属性:用来获取或设置窗体的名称,在应用程序中可通过Name 属性来引用窗体. (2) WindowState 属性: 用来获取或设置窗体的窗口状态. ...