WebSphere+Spring+Hazelcast环境下,调度线程OOME后仍引发其他线程异常的疑问
我们的生产Web服务器基于IBM WebSphere、Spring IoC、Spring MVC和Hazelcast构建,采用Hazelcast作为Spring Session的实现。单个调度线程每60秒执行一次健康检查。在执行大型Excel导出任务期间,抛出了大量OutOfMemoryError(OOME)。但任务成功完成后,多数服务器请求失败,堆栈跟踪如下:
[LargeThreadPool-thread-6481] ERROR xxxxFilter Exception captured in filter scope: org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java Heap space at org.springframework.web.DispatcherServlet.doDispatch(DispatcherServlet.java:982) at org.springframework.web.DispatcherServlet.doService(DispatcherServlet.java:901) xxx spring filter stack xxx IBM Websphere stack Caused by: java.lang.OutOfMemoryError: Java heap space at java.lang.Class.getDeclaredFieldsImpl at java.lang.Class.getDeclaredFields at java.io.ObjectStreamClass.getDefaultSerialFields xxx at com.hazelcast.client.impl.proxy.ClientMapProxy.setAsync xxx at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run
WebSphere在任务执行期间生成了4个phd和java core文件,任务完成后未再生成转储文件。
调试Hazelcast代码后发现,其捕获了OOME以进行客户端生命周期管理,但未将异常重新抛出到其他线程可访问的位置。
疑问:调度线程在OOME发生后应该已经终止,为何它的执行会反复成为其他线程异常的根因?
调度线程并未真正终止
ScheduledThreadPoolExecutor默认会在任务抛出未捕获异常时移除后续调度,但如果Hazelcast内部捕获了OOME且未重新抛出,调度线程本身不会因未捕获异常终止。该线程会持续存在,且之前OOME导致堆内存处于高危状态(如大量未回收对象、内存碎片),后续每次执行健康检查时,都可能再次触发OOME,而Hazelcast的异常捕获逻辑将其内部消化,但线程池环境已被污染。堆内存持续处于不健康状态
大型Excel导出任务虽已完成,但OOME发生时可能破坏JVM内存回收机制:部分对象因异常中断无法被正常标记回收,或产生大量内存碎片,导致后续即使总空闲内存充足,也无法分配连续空间给新请求。此时任何内存分配操作(如Hazelcast序列化时的ObjectStreamClass.getDefaultSerialFields)都会触发OOME,而这些操作关联到调度线程的健康检查任务,因此堆栈显示根因为调度线程的执行。Hazelcast客户端状态被污染
Hazelcast捕获OOME后进行客户端生命周期管理,但可能未彻底清理受影响的资源或状态。后续业务线程处理请求时使用Hazelcast客户端,会访问到这些异常状态的资源,进而触发OOME,堆栈会回溯到最初污染客户端状态的调度线程任务。WebSphere线程池复用带来的状态继承
WebSphere线程池会复用线程,调度线程所在的LargeThreadPool可能被其他请求任务复用。该线程因之前的OOME存在异常状态(如未清理的线程局部变量、未释放的内存资源),后续复用该线程处理请求时会继承这些异常状态,触发新的OOME,表现为调度线程的执行成为根因。
内容的提问来源于stack exchange,提问作者NYoung

