Tomcat9升级后异步进程触发请求对象已回收IllegalStateException问题
代码库核心事件突然报错,错误信息统一,仅run()方法的参数类存在差异:
2024-11-06 22:32:12,435 AuditService ERROR accounts.service.audit.AuditService run(ProfileChangeRequestDenied): java.lang.IllegalStateException: The request object has been recycled and is no longer associated with this facade
这段代码多年未改动,升级到Tomcat 9后才出现该错误。调研发现Tomcat 9将org.apache.catalina.connector.RECYCLE_FACADES的默认值从false改为了true。目前已知问题是代码尝试访问已被回收的请求对象,但该基类被广泛使用,希望先通过临时补丁争取时间进行彻底修复。
引发错误的ServiceEvent类精简版本如下,调试定位到错误出现在getUserAgent方法的servletRequest.getHeader调用处:
import javax.servlet.http.HttpServletRequest; public abstract class ServiceEvent { private final HttpServletRequest servletRequest; public ServiceEvent(HttpServletRequest servletRequest) { this.servletRequest = servletRequest; } public String getUserAgent() { String userAgent = null; if (null != servletRequest) { userAgent = servletRequest.getHeader(USER_AGENT); } return (userAgent == null) ? UNKNOWN : userAgent; } public abstract String getDetails(); }
尝试在Tomcat环境变量配置脚本中添加以下配置,但未生效:
CATALINA_OPTS="-D-Dorg.apache.catalina.connector.RECYCLE_FACADES=true ${CATALINA_OPTS}"
疑问
- 我的假设是否正确?修改
RECYCLE_FACADES的值能否恢复原有行为? - 我的配置方式是否正确?
- 是否还有其他遗漏的要点?
注:从事软件工程多年,但使用Java仅约一年。
1. 假设正确性
你的假设完全正确。Tomcat 9把RECYCLE_FACADES默认值改为true后,请求处理完成会立即回收请求facade对象,此时再调用该对象的方法就会抛出IllegalStateException。将该参数设为false就能恢复Tomcat 8及更早版本的行为:请求facade不会被主动回收,哪怕请求处理完成,仍然能访问其方法(虽然这会带来内存泄漏风险,但确实能临时解决当前报错问题)。
2. 配置方式错误
你的配置里多了一个-D,JVM参数的正确格式是单个-D前缀,而且要设为false才会改变默认行为,正确配置应为:
CATALINA_OPTS="-Dorg.apache.catalina.connector.RECYCLE_FACADES=false ${CATALINA_OPTS}"
另外要确保配置脚本生效:
- Linux/Unix系统:确认修改的是
catalina.sh或对应环境变量脚本,重启Tomcat后用ps aux | grep tomcat查看进程参数,确认该配置已加载。 - Windows系统:修改
catalina.bat后同样需要重启Tomcat验证。
3. 其他遗漏要点
- 临时补丁的风险:把
RECYCLE_FACADES设为false会导致请求facade对象无法及时回收,长期运行可能引发内存泄漏甚至OutOfMemoryError,这只是权宜之计,必须尽快推进彻底修复。 - 彻底修复方向:不要在请求处理完成后持有
HttpServletRequest的引用,应该在请求生命周期内(比如Controller/Service方法执行时)提前提取需要的信息(比如User-Agent),存储到ServiceEvent类中,而非直接持有请求对象。例如修改ServiceEvent:
import javax.servlet.http.HttpServletRequest; public abstract class ServiceEvent { private final String userAgent; public ServiceEvent(HttpServletRequest servletRequest) { this.userAgent = servletRequest != null ? servletRequest.getHeader(USER_AGENT) : UNKNOWN; } public String getUserAgent() { return userAgent; } public abstract String getDetails(); }
- 验证参数是否生效:可以在代码中添加日志输出
System.getProperty("org.apache.catalina.connector.RECYCLE_FACADES"),确认参数是否被正确加载。
内容的提问来源于stack exchange,提问作者commadelimited

