直接新建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
• 执行 lsof -p
• 执行 jstack
• 添加 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
• 在测试环境压测:固定线程数循环发消息,观察 /proc/










