You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

排查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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 23:52:06