JDK 序列化问题排查
- 标签:后端、线上问题排查
🏛 业务架构师
JDK 原生序列化的问题排查和替换,我按"现象 → 排查 → 结论 → 选型"讲。
典型故障现象
- 跨服务调用报
InvalidClassException(serialVersionUID 不一致——一边改了类没改版本号) - 反序列化直接失败
ClassNotFoundException(消费方没有对应类) - RPC 响应体异常巨大、耗时高
- 安全告警:反序列化漏洞(CVE 链,如 Commons-Collections gadget)被扫出
排查手段
- 开启
-Dsun.io.serialization.extendedDebugInfo=true拿到序列化对象路径细节 - 抓包/落盘序列化字节流,用工具分析对象构成,通常是某个大集合或嵌套对象把体积撑爆
- 核对双方类的字段和 serialVersionUID 定义
JDK 序列化的原罪(为什么必须换)
- 体积大:携带类元信息,同样数据比 JSON 大 3~5 倍,比 Protobuf 大 5~10 倍——带宽和存储的真金白银
- 性能差:反射密集,大对象序列化秒级
- 跨语言不通:Java only,和任何非 JVM 系统对接就死
- 不安全:反序列化漏洞是 RCE 重灾区,攻击者构造恶意字节流直接执行代码——这已经是安全合规问题,不只是技术选型
替换选型(按场景)
- 对外 API、可读性要求:JSON(Jackson)
- 内部高性能 RPC:Protobuf / Kryo(体积小一个数量级)
- 缓存对象:JSON 或 Protobuf,配合版本兼容策略(新增字段可选、老消费方跳过未知字段)
收尾:我治理存量系统的原则是新代码一律禁用 JDK 序列化(代码规约 + 静态扫描卡点),存量按故障驱动逐步替换——全面重写的历史包袱改造,投入产出通常不划算,除非它已经造成过资损或正在被安全审计追着跑。
🏗 系统架构师
JDK 序列化的问题我按"踩坑点 → 排查 → 替代"来讲。
典型故障现象:RPC/Dubbo 老版本调用报 java.io.InvalidClassException(local class incompatible, serialVersionUID 不同)、序列化后体积暴涨、CPU 被序列化吃满、反序列化失败导致服务间调用大面积报错。
排查路径:
- 看异常栈确定是序列化还是反序列化阶段,
serialVersionUID不一致是最高频原因——类加了字段但两端版本不同步 - 检查被序列化对象是否实现了
Serializable,嵌套的子对象漏实现会运行时才炸 - 静态和
transient字段不参与序列化,排查"字段莫名为 null"时先想到它 - 抓包或看 MQ 消息体大小,JDK 序列化带大量类元数据,同样的 POJO 体积是 JSON 的 3~5 倍、Protobuf 的 5~10 倍
JDK 序列化的固有缺陷(这也是面试官想听的):
- 体积大、性能差(反射 + 全量类元数据)
- 跨语言不可用,只限 Java
- 安全问题:反序列化漏洞(利用 gadget chain RCE),这也是官方逐步废弃它的原因
- 无法向前兼容,类演进就炸
治理方案:新链路一律换 JSON(调试友好)或 Protobuf/Kryo/Hessian2(性能敏感,Dubbo 默认 hessian2);存量系统过渡期显式声明 serialVersionUID 锁定版本,只增字段不删不改类型;对外暴露的接口绝不接受 JDK 序列化输入,防反序列化攻击。
💻 全栈开发专家
JDK 序列化线上故障我处理过的典型三类,最后都换掉了 JDK 序列化。
三类问题
- serialVersionUID 不一致:两端类版本不同(加了个字段但没固定 UID),反序列化直接抛
InvalidClassException。排查:报错栈里有明确的 localVersionUID 与 stream serialVersionUID,serialver命令可算。 - 类结构不兼容:即使 UID 固定,字段类型变了/删了字段,读出的对象缺字段或抛
StreamCorruptedException,问题隐藏在二次使用时才爆。 - 体积大、性能差:JDK 序列化带大量类元数据,比 JSON/Protobuf 大 3~5 倍、慢一个量级,Redis 里存这种 value 直接拖垮网络和 GC。
排查手段
- 二进制不可读,先
xxd看文件头(JDK 序列化魔数ACED0005),确认真的是 JDK 格式而不是被中间件二次包装。 - 开
-Djava.io.serialization.debug或在 readObject 处埋点打印类名和 UID。 - 跨服务版本不一致时,把双方 jar 包的类拉下来
serialver比对。
根治:换序列化协议
yaml
# Dubbo 换 hessian2(默认)/fastjson2
dubbo.protocol.serialization: hessian2缓存对象统一 JSON(加类型字段)或 Protobuf;跨语言、演进频繁的场景 Protobuf 的字段编号兼容性远好于 JDK 序列化。还有一点安全:JDK 反序列化有 gadget 链 RCE 历史(Commons-Collections),攻击面本身就是换掉它的理由。