正确配置ActiveMQ以避免Producer内存泄漏

时间:2019-01-11 08:56:23

标签: java spring spring-integration activemq

我不是ActiveMQ专家,但是我尝试在Internet上搜索很多类似问题,但我仍然很困惑。我有以下问题。

在Tomcat 8.x,Java 8,Spring Framework 4.3.18中运行Web应用程序。 我的Web应用程序使用org.apache.activemq:activemq-spring:5.11.0依赖性通过ActiveMQ发送和接收消息。

我正在以这种方式建立ActiveMQ连接工厂:

<amq:connectionFactory id="amqJmsFactory" brokerURL="${jms.broker.url}" />
<bean id="jmsConnectionFactory"
    class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
    <property name="connectionFactory" ref="amqJmsFactory" />
    <property name="maxConnections" value="2" />
    <property name="idleTimeout" value="60000" />
    <property name="timeBetweenExpirationCheckMillis" value="600000" />
    <property name="maximumActiveSessionPerConnection" value="10" />
</bean>

已设置最后一个属性(maximumActiveSessionPerConnection)来尝试解决以下问题(默认值似乎是500,这是非常高的恕我直言),但我不确定它是否真的有帮助,因为我仍然出现OutOfMemory错误。

此连接工厂由侦听器容器工厂引用:

<jms:listener-container factory-id="activationJmsListenerContainerFactory"
    container-type="default" connection-factory="jmsConnectionFactory"
    concurrency="1" transaction-manager="centralTransactionManager">
</jms:listener-container>

通过一个Spring Integration 4.3.17入站适配器:

<int-jms:message-driven-channel-adapter id="invoiceEventJmsInboundChannelAdapter" 
    channel="incomingInvoiceEventJmsChannel"
    connection-factory="jmsConnectionFactory"
    destination-name="incomingEvent"
    max-concurrent-consumers="2"
    transaction-manager="customerTransactionManager"
    error-channel="unexpectedErrorChannel" />

和两个出站适配器:

<int-jms:outbound-channel-adapter id="invoiceEventJmsOutboundChannelAdapter"
    channel="outgoingInvoiceEventJmsChannel" destination-name="outgoingEvent"
    connection-factory="jmsConnectionFactory" explicit-qos-enabled="true" delivery-persistent="true" 
    session-transacted="true" />

<int-jms:outbound-channel-adapter
    id="passwordResetTokenSubmitterJmsOutboundChannelAdapter"
    channel="passwordResetTokenSubmitterJmsChannel"
    destination-name="passwordReset"
    connection-factory="jmsConnectionFactory" explicit-qos-enabled="true"
    delivery-persistent="false" session-transacted="false" />

一切正常,但我观察到的是ActiveMQ作为消息生产者(用于invoiceEventJmsOutboundChannelAdapter适配器)正在内存中累积大量对象,并在我的应用程序中导致OutOfMemory错误。我的消息可能长一些KB,因为它们的有效载荷是XML文件,但是我不希望很长时间保持这么大的内存。

这是我对最近一次OutOfMemory错误(使用Eclipse MAT进行调查)产生的堆转储的发现。找到了两个泄漏嫌疑人,都导致了ConnectionStateTracker

这是两个累加器之一:

Class Name                                                                                                  | Shallow Heap | Retained Heap
-------------------------------------------------------------------------------------------------------------------------------------------
java.util.concurrent.ConcurrentHashMap$HashEntry[4] @ 0xe295da78                                            |           32 |    58.160.312
'- table java.util.concurrent.ConcurrentHashMap$Segment @ 0xe295da30                                        |           40 |    58.160.384
   '- [15] java.util.concurrent.ConcurrentHashMap$Segment[16] @ 0xe295d9e0                                  |           80 |    68.573.384
      '- segments java.util.concurrent.ConcurrentHashMap @ 0xe295d9b0                                       |           48 |    68.573.432
         '- sessions org.apache.activemq.state.ConnectionState @ 0xe295d7e0                                 |           40 |    68.575.312
            '- value java.util.concurrent.ConcurrentHashMap$HashEntry @ 0xe295d728                          |           32 |    68.575.344
               '- [1] java.util.concurrent.ConcurrentHashMap$HashEntry[2] @ 0xe295d710                      |           24 |    68.575.368
                  '- table java.util.concurrent.ConcurrentHashMap$Segment @ 0xe295d6c8                      |           40 |    68.575.440
                     '- [12] java.util.concurrent.ConcurrentHashMap$Segment[16] @ 0xe295d678                |           80 |    68.575.616
                        '- segments java.util.concurrent.ConcurrentHashMap @ 0xe295d648                     |           48 |    68.575.664
                           '- connectionStates org.apache.activemq.state.ConnectionStateTracker @ 0xe295d620|           40 |    68.575.808
-------------------------------------------------------------------------------------------------------------------------------------------

如您所见,ConnectionStateTracker的实例保留了大约70 MB的堆空间。 ConnectionStateTracker有两个实例(我猜每个出站适配器一个),它们总共保留了约120 MB的堆空间。他们在两个ConnectionState实例中进行累积,这些实例依次具有“会话”图,其中包含总计10个SessionState实例,而这些实例又ConcurrentHashMap个生产者持有总共1,258 ProducerState个实例。它们在它们的transactionState字段中保留了那120 MB的堆,该字段的类型为TransactionState,而该字段又有一个commands ArrayList,似乎保留了所有消息我要寄出。

我的问题是:为什么ActiveMQ将已经发出的消息保留在内存中?将所有这些消息保留在内存中还存在一些安全问题。

3 个答案:

答案 0 :(得分:1)

这是我终于解决这个问题的方式。

我认为这里的主要问题是bug AMQ-6603。因此,我们要做的第一件事是升级到ActiveMQ 5.15.8。我认为这足以解决泄漏问题。

但是,我们发现不鼓励将池化连接工厂与侦听器容器工厂一起使用后,也对配置进行了一些更改。我认为ActiveMQ文档令人困惑,正确的JMS配置比应该的要复杂。无论如何,如果您阅读DefaultMessageListenerContainer文档,则会阅读:

  

请勿将Spring的org.springframework.jms.connection.CachingConnectionFactory与动态缩放结合使用。理想情况下,根本不要将其与消息侦听器容器一起使用,因为通常最好让侦听器容器本身在其生命周期内处理适当的缓存。另外,停止和重新启动侦听器容器仅适用于独立的本地缓存的Connection,而不适用于外部缓存的Connection。

然后,同样必须适用于ActiveMQ PooledConnectionFactory。但是,ActiveMQ文档改为:

  

Spring的MessagListenerContainer应该用于消息消费。这提供了MDB的所有功能-有效的JMS使用和消息侦听器的池化-而不需要完整的EJB容器。

     

您可以使用activemq-pool org.apache.activemq.pool.PooledConnectionFactory为使用者集合高效地建立连接和会话的池,也可以使用Spring JMS org.springframework.jms.connection.CachingConnectionFactory来达到相同的效果。

因此,ActiveMQ文档提出了相反的建议。为此,我打开了bug AMQ-7140。因此,我现在仅向JMS 客户端注入PooledConnectionFactory(或Spring CachingConnectionFactory),以及普通的非缓存ActiveMQ连接工厂(使用{ {1}})使用Spring <amq:connectionFactory>或Spring Integration <jms:listener-container>构建侦听器容器工厂时,我宁愿设置它们与并发性和缓存级别有关的属性。

另一个困难是,我们要将本地构造的事务管理器传递给侦听器容器工厂,以使JMS消息提交与数据库提交同步。确实,这会导致侦听器容器工厂在默认情况下完全禁用其连接缓存机制,除非您还设置了显式缓存级别(请参阅<int-jms:message-driven-channel-adapter> Javadoc)。

我通过说很难获得解决方案来结束这个答案,特别是因为任何ActiveMQ开发人员在mailing list或问题跟踪程序上都没有得到任何反馈,即使恕我直言,可能被视为安全问题。这种无所作为,再加上缺乏替代方案,使我开始思考是否应该在下一个项目中考虑使用JMS。

答案 1 :(得分:0)

我们在客户的站点上遇到了同样的问题。

看一下ActiveMQ代码,ConnectionStateTracker中有一个类RemoveTransactionAction,它会响应OpenWire RemoveInfo(类型12)消息来删除条目。该消息似乎是ActiveMQ收到消息后生成的。

答案 2 :(得分:0)

TL; DR

在您的.*上禁用anonymousProducers


我们有一个非常类似的问题,即使用JMS事务时,服务耗尽了内存,并且PooledConnectionFactory配置了最多8个连接。

但是,我们并没有使用PooledConnectionFactory或Spring,而只发送了一个生产者。

此生产者负责从批处理作业中发送大量消息,我们发现当连接故障转移时,这些消息将保留在附加到旧连接的DefaultMessageListenerContainer上。经过多次故障转移后,这些消息将在旧的连接上累积,直到我们耗尽堆。

从内存中清除这些消息的唯一方法似乎是在提交JMS事务后关闭生产者。这将从ConnectionStateTracker上的ProducerState中删除SessionState实例。

SimonD在回答中提到的对ConnectionStateTracker的调用(在提交JMS事务后自动发生)只是从RemoveTransactionAction中删除了TransactionState,但仍然留下了生产者及其ConnectionState对象上显示消息。

不幸的是,在生产者上调用SessionState不能与close()一起使用,因为默认情况下它使用匿名生产者-调用PooledConnectionFactory方法对匿名生产者无效。您必须先致电close()上的setUseAnonymousProducers(false),以关闭生产者才能生效。

还值得指出的是,尽管PooledConnectionFactory的JavaDoc建议,您必须在生产者上调用close()-在会话中调用close()不会导致生产者被关闭。 。而是在生产者上调用ActiveMQSession方法。

相关问题