直接新建Producer而不复用会导致堆外内存与系统资源泄漏,因每个实例独占Netty EventLoopGroup、Channel、ByteBuf及文件描述符,且不随GC释放;典型表现为RES内存上涨、CLOSE_WAIT连接激增、“Too many open files”错误。

直接新建 Producer 而不复用,是 RocketMQ(及多数 MQ 客户端)中典型的物理内存泄漏诱因。根本原因在于:每个 Producer 实例内部会创建独立的 Netty EventLoopGroup、线程池、堆外缓冲区(PooledByteBufAllocator)、心跳 Channel 及 MappedByteBuffer(如 NameServer 请求缓存),这些资源均占用 JVM 堆外内存(Direct Memory)和系统级资源(如文件描述符、线程栈),且不会随 Producer 对象被 GC 自动释放。
重点看三个泄漏表现
• 进程 RES 内存持续上涨,但 JVM 堆内存(-Xmx)稳定:说明泄漏发生在堆外,jstat 看不到异常,但 top/htop 显示 java 进程物理内存不断增长;
• netstat 显示大量 ESTABLISHED 或 CLOSE_WAIT 连接:每个 Producer 默认建立独立到 Broker 和 NameServer 的长连接;
• 系统级报错如 “Too many open files” 或 “Cannot create new thread”:EventLoopGroup 线程未关闭、Channel 未释放、fd 未归还。
快速定位步骤(5 分钟内)
• 执行 pmap -x <pid> | grep anon | tail -10:观察匿名内存(anon)区域是否随消息发送次数线性增长;
• 执行 lsof -p <pid> | wc -l:对比启动后与发送 1000 条消息后的文件描述符数量,若增长数百甚至上千,基本确认连接泄漏;
• 执行 jstack <pid> | grep "NioEventLoop" | wc -l:检查 NioEventLoop 线程数是否随 Producer 创建次数增加(每个 Producer 默认启 3 个 worker 线程);
• 添加 JVM 参数 -Dio.netty.leakDetection.level=PARANOID 启动,发几条消息后观察日志——若出现 “LEAK: ByteBuf.release() was not called” 提示,即证实 Netty 缓冲区泄漏。
代码层确认与修复
• 检查所有 Producer 创建点,是否形如:
DefaultMQProducer producer = new DefaultMQProducer("group");
producer.start(); // 每次都 new + start
• 正确做法:
– 全局单例持有 Producer(Spring 中声明为 @Bean,scope=Singleton);
– 确保应用关闭时调用 producer.shutdown()(该方法会释放 Netty Group、关闭所有 Channel、清理定时任务);
– 若需多组 Producer(如多 Topic 多集群),也应按逻辑分组复用,而非每次请求 new;
– 禁止在 for 循环、RPC 方法体、HTTP 接口内创建 Producer。
补充验证手段
• 启动时加参数 -XX:MaxDirectMemorySize=256m,触发 OOM 时自动生成堆转储(配合 -XX:+HeapDumpOnOutOfMemoryError),用 MAT 查看 DirectByteBuffer 实例数及引用链,通常可追溯到 DefaultMQProducer → MQClientInstance → NettyRemotingClient → PooledByteBufAllocator;
• 使用 Arthas 的 watch 命令监控 DefaultMQProducer 构造方法调用频次:
watch org.apache.rocketmq.client.producer.DefaultMQProducer <init> '{params,returnObj}' -n 5;
• 在测试环境压测:固定线程数循环发消息,观察 /proc/<pid>/status 中 VmRSS 和 Threads 字段是否持续上升。

















