Netty 5新Buffer API详解
admin
2024-04-03 06:52:42
0次

目录

  • 前言
  • 原API的问题
  • 新API的设计目标
  • 新API的改动

前言

Netty 5的第一个alpha版本于2022/5/17发布。不同于老早发布但后面长期封存的ForkJoinPool版本,这次的新Netty 5改动相对3到4的升级来说没有那么大,侧重点放在了更安全好用的Buffer API和其他一些API的优化上。本文介绍的内容正是这次新版本的重头戏——新的Buffer API。

原API的问题

现存的Netty ByteBuf API已经使用多年,随着时间推移,类库逐渐变得臃肿庞大。导致以下问题:

  1. 它没有利用Java 6之后的新特性,例如try-with-resources。
  2. 太多的实现类和很深的类层次,以至于JIT很难优化,并增加了性能负担。
  3. 需要重新设计与Java新版本匹配的API。为了实现更安全的native内存管理,Java 14提供了MemorySegment功能,以取代原来Unsafe类提供的API。在不久前发布的Java 19中,该功能还处于preview阶段,所以Netty 5也会等正式版发布后再跟进实现,不过新API已经对此做好了设计上的预留。

新API的设计目标

  1. 安全的内存管理。每个buffer都有自己的所有者,不允许其他人破坏与污染。这一点上与MemorySegment API保持一致,因为这也是后者使用的基本要求。
  2. 引导正确使用。用简单的方式引导编码者正确使用Buffer API,减少误用情况。例如将资源放在try-with-resource中管理生命周期。
  3. 让简单的事情保持简单,复杂的事情可以实现。合理的默认值和符合直觉的命名让上手变得容易;同时对于高级使用者来说,也支持高阶的用法不受限制。
  4. 易用的符合直觉的API。需要比老版本更容易使用,工作模型中包含尽量少的隐藏状态。
  5. 高性能。与老版本一样保证API的高性能。

新API的改动

  1. 引用计数:所有buffer实现Autocloseable接口,能够使用try-with-resource来管理。每次buffer的分配都需要对应一次close()的释放,每次Send.receive()也需要对应close()。引用计数不再通过公共API暴露对外,但仍在内部保留,用于计算内存占用(尤其是堆外内存)。现在对外的是open/closed状态,它们的改变不是线程安全的。
 try (Buffer buf = allocator.allocate(8)) {// Access the buffer.
} // buf is deallocated here.
  1. 默认开启Cleaner跟踪:老版本的Netty为了性能考虑,默认关闭Cleaner。新版本则是默认开启,好处是:当buffer被忘记关闭时,Cleaner线程会自动回收内存,这样可以避免内存泄漏的发生。

小知识——Java对堆外内存的管理逻辑:

  1. 通过Unsafe分配的堆外内存,只有在Full GC或调用System.gc()时才会回收。
  2. 通过创建DirectByteBuffer分配的堆外内存,会自带一个Cleaner对象,当DirectByteBuffer对象被回收时,会触发Cleaner对象的clean()方法对堆外内存回收。
    在第一种情况下,如果一直不触发Full GC,那么当发生内存泄漏时,内存会逐渐耗尽。(如果在JVM启动参数中设置了最大堆外内存大小,那么达到阈值后也会触发Full GC)
  1. 去掉slice:原来的slice()和duplicate()可以让多个buffer共享同一块内存。现在去除内存共享,让引用计数的规则更加简单。
  2. Buffer接口:原有的ByteBuff抽象类和大量的实现类都被统一的Buffer接口取代;新的Buffer接口只有两种实现,一种基于MemorySegment,一个是CompositeBuffer。通过Buffer接口而非实现类来提供public访问。
  3. Allocator接口:原有的ByteBufAllocator接口被Allocator取代。堆内/堆外、池化/非池化两个维度的不同buffer都通过Allocator的静态工厂方法来分配。下面代码演示了Allocator接口的用法:
try (BufferAllocator allocator = BufferAllocator.heap();Buffer buf = allocator.allocate(8)) {// Access the buffer.
}
  1. CompositeBuffer:老版本的CompositeByteBuf是对外暴露的类,新版本用CompositeBuffer取代了它,不过大部分方法都隐藏在Buffer接口下。这样做的好处是,不论是组合还是非组合buffer,它们的对外方法都被抽象成一致的,从而避免了许多地方需要对是否为组合buffer做条件判断。另一方面,有些方法是组合buffer特殊的,它们也提供在了CompositeBuffer类中。组合buffer和普通buffer一样,也需要知道它们的allocator,因为在实现ensureWritable()时需要。因此compose()方法需要BufferAllocator作为第一个参数:
try (Buffer x = allocator.allocate(128);Buffer y = allocator.allocate(128)) {return CompositeBuffer.compose(allocator, x.send(), y.send());
}

通过compose()生成的组合buffer会取得子buffer的所有权。

  1. capacity管理:老版本有capacity()和maxCapacity()两个独立的概念,而且capacity是可以动态修改的。新版本只有capacity(),不再有maxCapacity()。只有ensureWritable()和CompositeBuffer.extendWith()方法可以增加capacity。ensureWritable()需要先获取所有权,否则会抛出异常。write*()方法不再自动增加capacity,若耗尽则会抛出异常。

  2. 字节顺序:老版本的ByteBuf.order(ByteOrder)会返回代表原始buffer视图的新buffer实例,而新版本的Buffer.order(ByteOrder)是直接在原buffer上修改,改变它的访问方法的字节顺序。对比之下,老版本API实现会造成额外的分配和包装buffer的性能开销。为了避免这部分开销,老版本还提供了get/set/read/write*LE()方法,如今也都不需要了。

  3. 用split()切分buffer:老版本的slice()让内存可以共享,但这会带来所有权混乱的问题。新版本用split()来取代,与slice()不同的是,用split()切分后的两个buffer彼此独立,他们各自拥有不同的内存块、capacity、offset和owner。在内存管理中使用了二级引用计数的机制:当切分出来的子buffer都关闭后,原始的buffer也会被关闭。

buf.writeLong(x);
buf.writeLong(y);
executor.submit(new Task(buf.split().send()));
buf.ensureWritable(512);

在上面的例子中,buf被切成两段,一段通过send()传给另一个线程做进一步处理,另一段(原始buf)保留原来的所有者,这使得它能够调用ensureWritable()方法而不会抛出异常。

  1. 用send()转移所有权:之前提到要支持try-with-resource管理buffer生命周期,但这只局限于本线程操作。如果我们需要在另一个线程关闭buffer,就需要显式地把buffer的所有权从一个线程转移到另一个。新版本提供了send()和receive()方法来发送和接收buffer的所有权。这里要注意两点:一是若只调用send()不调用receive(),则buffer最终会被Cleaner回收掉;二是send()会将buffer状态暂时置为不可用,receive()时再恢复状态。下面代码演示了send()和receive()的用法:
var send = buf.send();
executor.submit(() -> {try (Buf received = send.receive()) {// process received buffer...}
});

相关内容

热门资讯

AI做攻略、“双向奔赴”……国... 本文转自:人民网人民网记者 许维娜今年国庆假期,AI(人工智能)成了不少年轻人的“旅行搭子”。行前做...
合肥的这个“梗”, 翻篇了 社交平台的评论区,曾长期流传一句专属于合肥的黑色幽默。有人问合肥哪里好玩,本地网友半开玩笑地回复:合...
7万+旅游中高层都在的群,今天... 做旅游这行,资源卡位、渠道互通、项目落地——往往就差一个“对的机会”。我们的行业社群已聚集70,00...
一场帐篷音乐节,如何让井研从“... 帐篷音乐节现场封面新闻记者 周洪攀10月的研溪湿地,晚风里裹着柑橘的清甜与民谣的回响。夜幕降临,10...
白鹤翩翩落星愿,上海迪士尼度假... 10月9日,世界候鸟日前夕,上海迪士尼度假区星愿公园的湖风里多了一分白鹤的灵动。三座白鹤雕塑正式揭幕...
成都锦江边赏初秋枫叶飘飘 目前,成都锦江边国华街枫叶树红了。近年来,国华街附近的几条街都栽种了红枫树,是城区较集中看枫叶的点位...
GBTA:商务旅行正在复苏,但... 亚太正在成为全球商务旅行增长的重要引擎。在经历疫情后的快速反弹之后,全球商务旅行正在进入一个新的增长...
从景区焕新到节赛引流 邹城双节... 齐鲁网·闪电新闻10月9日讯 中秋、国庆“双节”假期,邹城市文化和旅游局提前谋划、周密部署,立足自身...
江山如此多娇 且看石楼今朝 金秋十月,黄河岸畔秋意盎然,丰收盛会喜迎国庆。本次石楼国庆文旅活动以“江山如此多娇,且看石楼今朝”为...
海南国庆假期接待游客475.4... 2026年国庆假期,海南接待游客475.40万人次,较2025年增长13.1%,较2024年增长15...