跟踪 Spring Azure qPID JMS 代码中的内存泄漏

Tracing memory leak in Spring Azure qPID JMS code

我试图在我们非常小且简单的 Spring 引导应用程序中跟踪和识别内存泄漏的根本原因。

使用如下: - Spring 启动 2.2.4 - azure-servicebus-jms-spring-boot-starter 2.2.1 -MSSQL

函数: 该应用仅调度 Azure ServiceBus 队列并存储数据并将数据发送到其他目的地。 这是一个小应用程序,因此它可以轻松启动 64 兆内存,尽管我通过 Xmx 选项给它最多 256 兆内存。重要注意事项是,队列正在使用 Spring 默认事务处理模式和专用 JmsTransactionManager 进行分派,JmsTransactionManager 实际上是 ChainedTransactionManager 的内部 TM 以及 dbTM 和其他出站 JMS TM。两个 JMS ConnectionFactory 对象都创建为 CachingConnectionFactory。

行为:

应用程序启动后似乎没问题。没有流量,所以我可以在日志中看到它正在打开事务并在检查队列时关闭 (jms:message-driven-channel-adapter)。

然而,一段时间后仍然没有流量,没有任何消息被消耗,内存开始攀升,如通过 JVVM 监控的那样。

抛出错误:

--2020-04-24 11:17:01.443 - WARN 39892 ---   [er.container-10] o.s.j.l.DefaultMessageListenerContainer  : Setup of JMS message listener invoker failed for destination 'MY QUEUE NAME HERE' - trying to recover. Cause: Heuristic completion: outcome state is rolled back; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JMS transaction; nested exception is javax.jms.IllegalStateException: The Session was closed due to an unrecoverable error.

...几分钟后它达到堆的 MAX,从那一刻起它在线程打开 JMS 连接时因 OutOfMemory 错误而失败。

--2020-04-24 11:20:04.564 - WARN 39892 ---   [windows.net:-1]] i.n.u.concurrent.AbstractEventExecutor   : A task raised an exception. Task: org.apache.qpid.jms.provider.amqp.AmqpProvider$$Lambda1/0x000000080199f840@1ed8f2b9
-
java.lang.OutOfMemoryError: Java heap space
        at java.base/java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:61)
        at java.base/java.nio.ByteBuffer.allocate(ByteBuffer.java:348)
        at org.apache.qpid.proton.engine.impl.ByteBufferUtils.newWriteableBuffer(ByteBufferUtils.java:99)
        at org.apache.qpid.proton.engine.impl.TransportOutputAdaptor.init_buffers(TransportOutputAdaptor.java:108)
        at org.apache.qpid.proton.engine.impl.TransportOutputAdaptor.pending(TransportOutputAdaptor.java:56)
        at org.apache.qpid.proton.engine.impl.SaslImpl$SwitchingSaslTransportWrapper.pending(SaslImpl.java:842)
        at org.apache.qpid.proton.engine.impl.HandshakeSniffingTransportWrapper.pending(HandshakeSniffingTransportWrapper.java:138)
        at org.apache.qpid.proton.engine.impl.TransportImpl.pending(TransportImpl.java:1577)
        at org.apache.qpid.proton.engine.impl.TransportImpl.getOutputBuffer(TransportImpl.java:1526)
        at org.apache.qpid.jms.provider.amqp.AmqpProvider.pumpToProtonTransport(AmqpProvider.java:994)
        at org.apache.qpid.jms.provider.amqp.AmqpProvider.pumpToProtonTransport(AmqpProvider.java:985)
        at org.apache.qpid.jms.provider.amqp.AmqpProvider.lambda$close(AmqpProvider.java:351)
        at org.apache.qpid.jms.provider.amqp.AmqpProvider$$Lambda1/0x000000080199f840.run(Unknown Source)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:510)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:518)
        at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:1050)
        at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74)
        at java.base/java.lang.Thread.run(Thread.java:835)

堆转储:

我在整个过程中拍了几个堆快照,看看增加了什么。 我可以看到数量可疑的 ConcurrentHashMap/String/Byte[] 个对象。

有没有人知道 clue/hint 此设置和库中可能有什么问题:Spring 启动、在 Azure JMS 依赖项等底层使用的 Apache qPid?非常感谢。

更新 #1 我有明确的证据表明问题出在 Spring 或 azure 服务总线启动库中 - 不会自动使用 qPid 客户端。 我会说库有错误而不是 Spring,这只是我的猜测。这是失败设置的样子:

  1. 有两个 JMS 目标和一个 DB,每个都有其事务管理器
  2. ChainedTransactionManager 环绕三个 TM。
  3. Spring 集成应用程序,它通过 jms:message-driven-channel-adapter 连接到 Azure ServiceBus 队列并在此组件上设置事务管理器(在第 2 点中创建)
  4. 启动应用程序,不需要队列中的流量,10 分钟后应用程序将因 OutOfMemoryError 而崩溃...在这 10 分钟内,我在调试级别查看日志,唯一发生的事情是打开和使用 ChainedTransactionManager 关闭事务...也如评论中所写,另一个重要条件是第三个 JMS TransactionManager ...使用 2 个 TM,它可以工作并且稳定,使用 3 个它会崩溃 ...

进一步的研究和采取的措施确定了最可能的根本原因 Spring CachingConnectionFactory class。一旦我删除它并仅使用本机类型,问题就消失了,内存消耗情况非常不同且健康。

我不得不说我使用标准构造函数创建了 CachingConnectionFactory 并且没有进一步配置行为。然而,根据我的经验,这些 Spring 默认值显然会导致内存泄漏。

过去,我在使用 ActiveMq 时遇到了内存泄漏,必须使用 CachingConnectionFactory 来解决,现在我在使用 CachingConnectionFactory[=] 时遇到了 Azure ServiceBus 的内存泄漏24=] .. 奇怪 :) 在这两种情况下,我都将其视为错误,因为无论是否涉及缓存,内存管理都应该是正确的。

将此标记为我的答案。

测试案例:当使用自己的TM接收和发送消息时出现问题,并且两个JMS connectionFactories都是CachedConnectionFactory类型。最后我测试了这个应用程序。使用 CachedConnectionFactory 类型的入站连接工厂和仅本地类型的出站...也没有内存泄漏。