SenderJMS服务推送XML文件中途停止,求原因及排查方案
问题分析与解决方案
背景概述
存在目录 /var/xyz/aa/clm/data/infiles/SenderJMS/CE/L3/,流程运行后会在此生成大量XML文件,需通过JMS队列推送至客户云服务器的远程节点。当XML文件数量达到2-3万时,SenderJMS服务抛出OutOfMemoryError并停止处理,重启后可断点续推。已将堆内存从500MB扩容至1GB,问题仍未解决。
相关配置信息
凭证信息
Queue Name: VS.REVFDX.CL10.PUB.LAS.TCF.L3 jndi.environment.property.key.java.naming.provider.url=ldap://apptstldap.corp.<client_name>.com:888/ou=messaging,dc=corp,dc=<client_name>,dc=com destination.jndiName=fxClientDestinationUID=D.REVFDX.DLRS.ToBeInvoiced connection.factory.userid= XYZ_UID connection.factory.password= PWD
JNDI配置
@Bean(name = "jndiTemplate") public JndiTemplate jndiTemplate() { JndiTemplate jndiTemplate = new JndiTemplate(); Properties environment = new Properties(); environment.setProperty("java.naming.factory.initial", "jndi_environment_property_key_java_naming_factory_initial"); environment.setProperty("java.naming.provider.url", "jndi_environment_property_key_java_naming_provider_url"); jndiTemplate.setEnvironment(environment); return jndiTemplate; }
队列通道
@Bean(name="channel1") public QueueChannel queueChannel() { return new QueueChannel(); }
文件轮询器
@Bean(name="fileListPoller1") public FileListPoller fileListPoller() { FileListPoller fp = new FileListPoller(); fp.setDirectoryPath(file_inbound_dir); fp.setExcludes(excludes); return fp; }
入站处理器
@Bean(name="inboundHandler2",initMethod="initWS") public InboundJMSHandlerWS inboundJMSHandlerWS() { InboundJMSHandlerWS inboundJMSHandlerWS = new InboundJMSHandlerWS(); inboundJMSHandlerWS.setChannel(queueChannel()); inboundJMSHandlerWS.setAddress(inbound_webservice_endpoint_address_4jms); return inboundJMSHandlerWS; }
出站处理器
@Bean(name="outboundJMSHandler1",initMethod="initThreadPool") public OutboundJMSHandler outboundJMSHandler() { OutboundJMSHandler outboundJMSHandler = new OutboundJMSHandler(); outboundJMSHandler.setChannel(queueChannel()); outboundJMSHandler.setThreadPoolSize(outbound_jmshandler_thread_pool_size); outboundJMSHandler.setJndiTemplate(jndiTemplate()); outboundJMSHandler.setJmsPoolSize(connection_pool_size); outboundJMSHandler.setConnectionFactoryJndiKey(connectionFactory_jndiName); outboundJMSHandler.setDestinationJndiKey(destination_jndiName); outboundJMSHandler.setInterfaceName(outbound_jms_interface_name); outboundJMSHandler.setTestLevel(outbound_jms_testLevel); outboundJMSHandler.setCnFactoryUID(connection_factory_userid); outboundJMSHandler.setCnFactoryPassword(connection_factory_password); outboundJMSHandler.setJmsMessageType(jms_message_type); return outboundJMSHandler; }
错误日志
Exception in thread "TIBCO EMS TCPLink Reader (Server-130801)" java.lang.OutOfMemoryError: Java heap space Exception in thread "<ClientName>JMSStarvedConsumerTimer" java.lang.OutOfMemoryError: Java heap space.
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=75M; support was removed in 8.0 Java HotSpot(TM) 64-Bit Server VM warning: MaxNewSize (512000k) is equal to or greater than the entire heap (512000k). A new max generation size of 511936k will be used.
可能的原因分析
1. 内存泄漏
扩容堆内存后问题依旧,说明不是单纯的堆不足,而是存在内存泄漏:
- 未释放的JMS资源:
OutboundJMSHandler中如果连接池、会话或消息对象未正确关闭,会导致内存占用持续上升。比如发送消息后未关闭会话,或连接池配置不合理导致连接堆积。 - 文件对象堆积:
FileListPoller可能一次性加载大量文件对象到内存,未及时清理。如果轮询器没有限制每次处理的文件数量,2-3万文件的元数据或内容会直接占满堆内存。 - 队列通道积压:
QueueChannel默认是无界队列(如果未设置容量),当出站处理速度跟不上入站轮询速度时,大量消息会积压在通道中,占用内存。
2. JVM参数配置不合理
日志显示MaxNewSize等于整个堆大小,这会导致老年代无空间可用,新生代垃圾回收后对象直接进入老年代,加速老年代内存耗尽。同时MaxPermSize参数在Java 8中已被废弃,应替换为MetaspaceSize。
3. 远程JMS队列阻塞
如果远程队列已满,JMS生产者发送消息时会被阻塞(取决于JMS客户端配置的超时时间),导致本地消息无法发送,堆积在内存中。这种情况下,内存占用会持续上升直到溢出。
是否需要排查推送时队列是否已满?
必须排查。远程队列满是导致内存溢出的常见原因:
- 当远程队列达到最大容量时,生产者发送消息会被阻塞或抛出异常,如果客户端未正确处理这种情况,会导致待发送的消息在本地内存中堆积(比如通道队列、线程池任务队列),最终耗尽堆内存。
- 可以通过JMS服务器的管理控制台查看队列的当前消息数、最大容量和消费者消费速度,确认是否存在队列满的情况。
建议的解决方案
- 排查内存泄漏:
- 使用JProfiler、VisualVM等工具生成堆转储快照,分析内存中占用最大的对象类型,定位泄漏点。
- 检查
OutboundJMSHandler中JMS资源的释放逻辑,确保会话、连接在使用后正确关闭,连接池配置合理(比如设置最大连接数和空闲超时)。
- 限制队列通道容量:
- 修改
QueueChannel的配置,设置有界队列,比如:@Bean(name="channel1") public QueueChannel queueChannel() { return new QueueChannel(new LinkedBlockingQueue<>(1000)); // 设置队列容量为1000 } - 当通道满时,可配置拒绝策略(比如丢弃或阻塞),避免无限制堆积。
- 修改
- 优化文件轮询器:
- 限制
FileListPoller每次处理的文件数量,比如设置maxMessagesPerPoll属性,避免一次性加载大量文件。 - 处理完文件后及时删除或移动,避免轮询器重复扫描同一批文件。
- 限制
- 调整JVM参数:
- 合理分配新生代和老年代内存,比如设置
-Xms1g -Xmx1g -XX:NewRatio=2(新生代占1/3,老年代占2/3),避免MaxNewSize等于堆大小的情况。 - 替换
MaxPermSize为-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m。
- 合理分配新生代和老年代内存,比如设置
- 监控远程队列状态:
- 配置JMS队列监控,当队列接近满额时触发告警,及时处理消费者消费慢的问题。
- 在发送消息时添加超时配置,避免长时间阻塞导致内存堆积。
内容的提问来源于stack exchange,提问作者PKSenapaty
相关产品推荐
相关产品推荐

