当前位置: 首页 > news >正文

Netty 针对 Java NIO Selector 优化:SelectedSelectionKeySet

SelectedSelectionKeySet

这是一个 Netty 针对 Java NIO Selector 的一个非常巧妙的性能优化。理解它的作用需要先了解原生 NIO Selector 的一个痛点。

在标准的 Java NIO 编程中,事件循环的流程大致是:

  1. 调用 selector.select() 方法,阻塞等待 I/O 事件。
  2. 当 select() 返回时,表示有事件发生。
  3. 调用 selector.selectedKeys() 方法,获取一个 Set<SelectionKey> 集合。这个集合包含了所有就绪的 SelectionKey
  4. 遍历这个 Set,处理每一个 SelectionKey 对应的事件。
  5. 处理完一个 SelectionKey 后,必须手动调用 iterator.remove() 或 set.remove(key) 将其从集合中移除,否则下次调用 select() 时,这个 key 仍然会出现在集合中,导致重复处理。

这里的性能瓶颈主要在于两点:

  • selector.selectedKeys() 返回的 Set:在 HotSpot JVM 的 sun.nio.ch.SelectorImpl 实现中,这个 Set 通常是一个 HashSet。每次遍历和 remove() 操作都会有相应的哈希计算和数据结构维护的开销。
  • GC 压力:频繁地创建迭代器(iterator())和 remove() 操作会产生不少的垃圾对象,在高并发场景下会给 GC 带来压力。

Netty 的目标是榨干服务器的每一分性能。为了解决上述瓶颈,Netty 设计了 SelectedSelectionKeySet,用它来替换掉 Selector 内部默认的 HashSet

我们来逐个分析 SelectedSelectionKeySet.java 的代码,看看它是如何做到优化的。

类定义与核心数据结构

final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {SelectionKey[] keys;int size;SelectedSelectionKeySet() {keys = new SelectionKey[1024];}
//...
  • extends AbstractSet<SelectionKey>: 它继承自 AbstractSet,表明自己是一个 Set。这是为了符合 Java Selector API 的要求,因为 Selector 内部的 selectedKeys 字段就是一个 Set 类型。
  • SelectionKey[] keys; 和 int size;: 这是整个优化的核心!它没有使用 HashSet 或任何复杂的集合类,而是直接使用一个简单的数组 keys 和一个整型 size 来存储就绪的 SelectionKey。这种数据结构极其简单高效。

add() 方法:高效添加

// ... existing code ...@Overridepublic boolean add(SelectionKey o) {if (o == null) {return false;}if (size == keys.length) {increaseCapacity();}keys[size++] = o;return true;}
// ... existing code ...private void increaseCapacity() {SelectionKey[] newKeys = new SelectionKey[keys.length << 1];System.arraycopy(keys, 0, newKeys, 0, size);keys = newKeys;}
// ... existing code ...

当 selector.select() 发现一个 Channel 就绪时,它会调用这个 add 方法将对应的 SelectionKey 加入集合。

  • 操作: 这里的 add 操作非常快,它只是简单地将 SelectionKey 放到数组的末尾,然后将 size 加一。这是一个 O(1) 的操作,没有任何哈希计算或复杂的树/链表操作。
  • 扩容increaseCapacity() 实现了动态扩容,当数组满了之后,会创建一个两倍大小的新数组,并将旧数据拷贝过去。这是一个常规操作,但由于初始容量较大(1024),在绝大多数情况下不会频繁触发。

remove() 和 contains() 方法

// ... existing code ...@Overridepublic boolean remove(Object o) {return false;}@Overridepublic boolean contains(Object o) {// ... 遍历数组查找 ...return false;}
// ... existing code ...
  • remove(Object o): 这个方法直接返回 false它不支持单个元素的移除。这是 Netty 优化的关键之一。Netty 并不需要像原生 NIO 那样一个个地移除 Key,它有更高明的办法。
  • contains(Object o): 实现了简单的线性查找,虽然是 O(n),但在 Netty 的使用场景中,这个方法很少被调用。

reset() 方法:颠覆性的“清空”操作

// ... existing code ...void reset() {reset(0);}void reset(int start) {Arrays.fill(keys, start, size, null);size = 0;}
// ... existing code ...

这是 SelectedSelectionKeySet 的精髓所在。

Netty 在处理完一轮就绪的 SelectionKey 之后,并不会去调用 iterator.remove()。相反,它会直接调用 reset() 方法。这个方法只是简单地将数组中从 0 到 size 的元素置为 null,然后把 size 设为 0

这个操作代替了原生 NIO 中成百上千次的 remove() 调用,将多次操作合并为一次高效的批量操作,极大地减少了方法调用和 CPU 开销,并且完全避免了 remove() 操作产生的垃圾对象。

如何将 SelectedSelectionKeySet "注入" Selector?

既然 JDK 的 Selector 内部实现是私有的,Netty 是如何用自己的 SelectedSelectionKeySet 替换掉默认的 HashSet 的呢?

答案是Java 反射

在 NioIoHandler的初始化过程中,有这样一段关键代码(逻辑类似):

// ... existing code ...final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {@Overridepublic Object run() {try {// 通过反射获取 SelectorImpl 内部的私有字段Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");// ...// 暴力破解访问权限selectedKeysField.setAccessible(true);publicSelectedKeysField.setAccessible(true);// 将我们自己的 selectedKeySet 实例设置进去selectedKeysField.set(unwrappedSelector, selectedKeySet);publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);// ...} catch (Exception e) {return e;}return null;}});
// ... existing code ...

通过 AccessController 和反射,Netty 强行打开了 SelectorImpl 的内部实现,将 selectedKeys 和 publicSelectedKeys 这两个字段(它们在不同 JDK 版本中可能指向同一个或不同的 Set 实例)都替换成了 Netty 自己创建的 SelectedSelectionKeySet 实例。

这样一来,当 selector.select() 将就绪的 key 放入 selectedKeys 集合时,实际上调用的是 SelectedSelectionKeySet.add() 方法。当 Netty 处理完事件后,就可以调用 selectedKeySet.reset() 来高效清空了。

总结

SelectedSelectionKeySet 是 Netty NIO 性能优化的一个典范。它体现了 Netty 团队对 JVM 和 JDK 内部实现的深刻理解。

  • 目的: 替换 JDK NIO Selector 内部低效的 HashSet,减少迭代和移除操作的开销,降低 GC 压力。
  • 核心思想: 使用简单的数组代替 HashSet,将 add 操作优化为 O(1) 的数组末尾添加。
  • 颠覆性创新: 用一次性的 reset() 操作代替了成百上千次的 remove() 调用,将清空集合的成本降到最低。
  • 实现方式: 通过 Java 反射,在运行时将自定义的 Set 实例 "注入" 到 Selector 内部,实现了对 JDK 底层行为的"偷梁换柱"。

这个小小的类,完美地展示了 Netty 为了追求极致性能所做的努力。


文章转载自:

http://5S6Jdgsf.yhrfg.cn
http://oYTKjk9s.yhrfg.cn
http://dkqaEt0B.yhrfg.cn
http://wKkIABOV.yhrfg.cn
http://wGPYO6G5.yhrfg.cn
http://ULhw2NVc.yhrfg.cn
http://Ew11KVFz.yhrfg.cn
http://dvEz1wXi.yhrfg.cn
http://R01vd3z8.yhrfg.cn
http://hIWNXKGS.yhrfg.cn
http://seaWaBek.yhrfg.cn
http://h7Wq2tsH.yhrfg.cn
http://s4fIM2of.yhrfg.cn
http://Y8zWzS48.yhrfg.cn
http://axbJow5p.yhrfg.cn
http://UJvzmSGZ.yhrfg.cn
http://9w1nfEjl.yhrfg.cn
http://mzgei71I.yhrfg.cn
http://8sGqiDRQ.yhrfg.cn
http://B9PZQXvC.yhrfg.cn
http://tgAiszyj.yhrfg.cn
http://29GbwAQl.yhrfg.cn
http://8wtsHlcL.yhrfg.cn
http://0Um4VtNt.yhrfg.cn
http://4vaYpKcQ.yhrfg.cn
http://MQ7xLbxP.yhrfg.cn
http://PGFr6czq.yhrfg.cn
http://PBbxCojd.yhrfg.cn
http://xG0CJbnt.yhrfg.cn
http://Zac8XUIl.yhrfg.cn
http://www.dtcms.com/a/381888.html

相关文章:

  • 抑制信号突变(模拟量采集+斜坡函数)
  • C语言入门指南:字符函数和字符串函数
  • JVM从入门到实战:从字节码组成、类生命周期到双亲委派及打破双亲委派机制
  • SQL-用户管理与操作权限
  • Airtable与Python:轻量级ETL数据管道实战
  • JavaScript 对象:一份全面的回顾
  • 逐时nc数据批量处理为日平均
  • ffmpeg推流测试
  • SQL注入常见攻击点与防御详解
  • 后端(FastAPI)学习笔记(CLASS 3):Tortoise ORM
  • C++-STL
  • Java 大视界 -- Java 大数据在智能家居场景联动与用户行为模式挖掘中的应用
  • XCKU15P-2FFVA1760I AMD 赛灵思 Xilinx Kintex UltraScale+ FPGA
  • 图论基础知识
  • DMA硬件架构解析:总线矩阵与核心组件
  • 从军用到掌心:固态硬盘(SSD)的演进与革命
  • 通俗解释redis高级:redis持久化(RDB持久化、AOF持久化)、redis主从、redis哨兵、redis分片集群
  • 【C++】类和对象——(上)
  • 解决Windows系统“‘php‘ 不是内部或外部命令”报错的完整指南
  • 用 Go 打造一个服务器资源指标采集器:结合 Prometheus Exporter 实战
  • Unity学习----【进阶】TextMeshPro学习(二)--进阶知识点(样式表,颜色渐变预设,精灵图片资源)
  • 从理论到落地:神经网络稀疏化设计构架中网络剪枝的深度实践与创新
  • ARM、AArch64、amd64、x86_64、x86有什么区别?
  • 机器学习项目-南方电网电力负荷预测
  • python标准库有哪些模块,简单总结下。
  • 文献阅读·MCformer:基于混合通道变换的多变量时间序列预测
  • 【软件操作】飞牛nas系统:笔记本息屏、合盖均不关机
  • 【SPI】【二】SPI控制器驱动代码详解
  • pandas读取复合列名列头及数据和处理
  • jenkins触发部署