我不是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将已经发出的消息保留在内存中?将所有这些消息保留在内存中还存在一些安全问题。
答案 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 JMSorg.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
方法。