排查Redisson存储JSF会话时的java.util.ArrayList$SubList序列化异常
定位Redisson存储JSF会话时ArrayList$SubList序列化异常的根源
问题背景
使用Redisson存储JSF 2.3应用会话时,抛出java.util.ArrayList$SubList的NotSerializableException,但日志仅显示Redisson相关调用栈,无法找到生成SubList的代码位置,且代码中未显式使用SubList。
报错日志
java.io.NotSerializableException: java.util.ArrayList$SubList org.redisson.command.CommandAsyncService.encodeMapValue(CommandAsyncService.java:669) org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:351) org.redisson.RedissonMap.encodeMapKeys(RedissonMap.java:1051) org.redisson.RedissonMap.putAllOperationAsync(RedissonMap.java:775) org.redisson.RedissonMap.putAllAsync(RedissonMap.java:716) org.redisson.RedissonMap.putAll(RedissonMap.java:669) org.redisson.tomcat.RedissonSession.save(RedissonSession.java:428) org.redisson.tomcat.RedissonSessionManager.store(RedissonSessionManager.java:419) org.redisson.tomcat.UpdateValve.invoke(UpdateValve.java:74) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364) org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624) org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650) org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Thread.java:748) caused by java.io.NotSerializableException: java.util.ArrayList$SubList java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) java.util.HashMap.internalWriteEntries(HashMap.java:1793) java.util.HashMap.writeObject(HashMap.java:1363) sun.reflect.GeneratedMethodAccessor239.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:498) java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1155) java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496) java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) java.util.LinkedHashMap.internalWriteEntries(LinkedHashMap.java:333) java.util.HashMap.writeObject(HashMap.java:1363) sun.reflect.GeneratedMethodAccessor239.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:498) java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1155) java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496) java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441) java.util.Collections$SynchronizedMap.writeObject(Collections.java:2693) sun.reflect.GeneratedMethodAccessor355.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:498) java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1155) java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496) java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) org.redisson.codec.SerializationCodec$2.encode(SerializationCodec.java:78) org.redisson.command.CommandAsyncService.encodeMapValue(CommandAsyncService.java:667) org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:351) org.redisson.RedissonMap.encodeMapKeys(RedissonMap.java:1051) org.redisson.RedissonMap.putAllOperationAsync(RedissonMap.java:775) org.redisson.RedissonMap.putAllAsync(RedissonMap.java:716) org.redisson.RedissonMap.putAll(RedissonMap.java:669) org.redisson.tomcat.RedissonSession.save(RedissonSession.java:428) org.redisson.tomcat.RedissonSessionManager.store(RedissonSessionManager.java:419) org.redisson.tomcat.UpdateValve.invoke(UpdateValve.java:74) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:364) org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:624) org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650) org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Thread.java:748)
定位方法
1. 断点追踪序列化过程
在java.io.ObjectOutputStream.writeObject0方法上设置条件断点,条件为obj instanceof java.util.ArrayList$SubList。当序列化触发时,程序会自动停在该SubList实例的序列化步骤,此时查看当前线程的调用栈,或者通过IDE的对象引用链功能,找到这个SubList是被会话中的哪个属性持有,进而定位到代码中生成它的位置。
2. 遍历会话属性检查
编写一个工具方法,在RedissonSession.save()执行前触发(比如通过Tomcat的Valve或者JSF的PhaseListener),遍历会话中的所有属性,递归检查每个属性及其嵌套对象的类型,一旦发现ArrayList$SubList类型,输出该属性的名称和对象的完整路径,直接定位来源。
示例代码片段:
private void checkSessionForSubList(HttpSession session) { Enumeration<String> attrNames = session.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = attrNames.nextElement(); Object attrValue = session.getAttribute(attrName); checkObjectForSubList(attrValue, "session." + attrName); } } private void checkObjectForSubList(Object obj, String path) { if (obj == null) return; if (obj instanceof java.util.ArrayList.SubList) { System.err.println("Found SubList at: " + path); // 可选:打印当前栈 new Exception().printStackTrace(); return; } // 处理集合和数组 if (obj instanceof Collection) { int index = 0; for (Object item : (Collection<?>) obj) { checkObjectForSubList(item, path + "[" + index + "]"); index++; } } else if (obj instanceof Object[]) { Object[] arr = (Object[]) obj; for (int i = 0; i < arr.length; i++) { checkObjectForSubList(arr[i], path + "[" + i + "]"); } } else { // 处理普通对象的字段 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); try { Object fieldValue = field.get(obj); checkObjectForSubList(fieldValue, path + "." + field.getName()); } catch (Exception e) { // 忽略访问异常 } } } }
3. 排查隐式生成SubList的场景
- JSF框架内部:JSF的组件如
UISelectItems、DataModel在处理分页、过滤数据时,可能调用ArrayList.subList()生成子列表,并将其绑定到会话中。可以检查页面中使用的列表组件,是否存在分页逻辑依赖于框架自动处理的情况。 - 第三方库:项目中使用的ORM框架、集合工具库(如某些分页查询工具)可能返回
SubList而非新的ArrayList。排查依赖库的文档或源码,确认是否存在此类返回值。 - 间接集合操作:即使没有显式调用
subList(),某些代码逻辑可能间接生成,比如List的切片操作、自定义的分块方法等,检查所有涉及列表处理的代码路径。
4. 堆内存分析
当异常发生前,使用JVM工具(如JProfiler、VisualVM)dump堆内存,然后在堆快照中搜索java.util.ArrayList$SubList实例,查看实例的引用链,找到它被会话对象持有的路径,从而定位到生成代码。
内容的提问来源于stack exchange,提问作者dssof
相关产品推荐
相关产品推荐

