selectionkey 的 attachment() 是易被忽视的内存泄漏点:必须显式调用 key.attach(null) 清理,否则强引用的大对象会长期驻留堆中;其生命周期需与 key 同步,不可依赖自动清理。

Java NIO 中 SelectionKey 的 attachment() 是一个常用但易被忽视的内存泄漏点:只要 key 未取消、未关闭、且 attachment 持有强引用(尤其是大对象或含闭包/上下文的对象),它就会长期驻留在堆中,导致泄漏。
attachment 生命周期必须与 SelectionKey 同步
attachment 不是自动清理的——JVM 不会因为 key 失效或 channel 关闭就自动清空它。你必须显式调用 key.attach(null) 或 key.attach(newObj) 来替换/清除旧引用。
- 在
key.isValid() == false时(如 channel 关闭、key 取消后),attachment 仍可能被保留,需主动置 null - 在
SelectionKey.cancel()后,key 进入“已取消”状态,但 attachment 仍在,直到下一次Selector.select()清理其内部队列(不保证及时) - 推荐在每次处理完事件(如 OP_READ / OP_WRITE)后,若 attachment 不再需要,立即
key.attach(null)
避免在 attachment 中持有外部强引用
常见泄漏场景是把 Handler、Context、ByteBuffer、甚至整个 Session 对象塞进 attachment,而这些对象又反向引用了 classloader、线程局部变量或静态资源。
- 优先使用轻量级数据结构:如
AtomicInteger、int[]、简单 POJO(无 listener、无回调、无 this 引用) - 若必须存业务对象,考虑用
WeakReference<yourobj></yourobj>包装,但注意 WeakReference 本身不解决循环引用问题 - 禁止将 lambda 表达式(尤其捕获了外围类实例)直接作为 attachment,它隐式持有了
this引用
统一清理入口:在 cancel 和 close 阶段强制 detach
不要依赖“某个地方会清理”,应在所有退出路径上做防御性清除。
- 在
channel.close()前,遍历关联的 keys(channel.keys()或维护的 key 集合),对每个 key 调用key.attach(null)再key.cancel() - 在
selector.close()前,调用selector.keys().forEach(k -> k.attach(null)),再 clear 所有 keys - 在自定义的
Handler或Connection类的destroy()方法中,明确清理其绑定的 key 的 attachment
用 JVM 工具验证是否泄漏
attachment 泄漏本质是普通对象泄漏,可用标准工具定位:
- 开启
-XX:+PrintGCDetails观察老年代增长趋势 - 用
jmap -histo:live <pid></pid>查看是否有大量业务对象残留,结合SelectionKeyImpl实例数比对 - 用 VisualVM / JProfiler 的 heap dump 分析,按 “referent of java.nio.channels.spi.AbstractSelectionKey.attachment” 路径筛选,找到谁在持有它
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











