一句话总结
序列化把对象转成字节流以便存储或网络传输。Java 原生序列化实现 Serializable 标记接口、按类元数据 + 字段值编码;serialVersionUID 控制版本兼容,transient 排除敏感/不可序列化字段,static 字段不属于对象不参与序列化。生产环境不推荐原生序列化:体积大、跨语言差、有反序列化安全漏洞——一般用 JSON(Jackson/Fastjson)或 Protobuf。
初级理解
基本用法:类实现 Serializable(纯标记接口,无方法),用 ObjectOutputStream.writeObject 写、ObjectInputStream.readObject 读。
中级深入
serialVersionUID 的作用:类结构写入字节流时带上这个版本号,反序列化时两边 UID 不一致直接抛 InvalidClassException。不显式声明时 JVM 按类结构自动计算——改动任何字段/方法都会导致 UID 变化,旧数据瞬间不可读。所以线上持久化/传输的 DTO 必须显式声明,并在"兼容性变更"时保持不变。
自定义序列化:类里定义 private 的 writeObject/readObject 方法,JVM 会反射调用,用于加密字段、字段重映射、集合容量控制等场景。
引用与循环引用:同一对象被多次写出时只写一次,后续用引用编号代替(防死循环也省体积);这也意味着 writeObject 两次同一对象,第二次读回的是同一个实例,但改了对象再写需要注意 reset()。
高级拓展
为什么生产不用原生序列化(三宗罪):
- 体积大:携带完整类元数据(全限定类名、字段描述),比 JSON/Protobuf 大数倍
- 跨语言差:编码格式是 JVM 私有约定,Go/Python 无法直接消费
- 安全漏洞:readObject 恢复对象时执行类自带逻辑,攻击者构造恶意字节流(Gadget Chain)可实现远程代码执行——历史上著名的 Apache Commons-Collections 反序列化 RCE、Weblogic/JBoss 反序列化漏洞皆源于此
主流替代方案选型:
| 方案 | 体积/性能 | 跨语言 | 场景 |
|---|---|---|---|
| JSON(Jackson/Fastjson2) | 中等/一般 | 好,可读 | HTTP 接口、调试友好 |
| Protobuf | 最小/最快 | 好,需 .proto | gRPC、内部 RPC、高吞吐 |
| Hessian | 较小/较快 | 较好 | Dubbo 默认之一 |
| Java 原生 | 大/慢 | 无 | 仅限可信内部场景 |
框架层的序列化坑:Jackson 的多态序列化默认丢失类型信息,需 @JsonTypeInfo + @JsonSubTypes(开启时注意指定合法子类白名单,防反序列化注入);Fastjson 曾因 autoType 全开出重大漏洞,Fastjson2 默认关闭并校验;Redis 场景用 GenericJackson2JsonRedisSerializer(自带 @class 信息)而非 JdkSerializationRedisSerializer。
实战场景
场景一:缓存反序列化 ClassNotFound
场景二:DTO 升级但消费方未同步发布
场景三:接口日志脱敏
面试模拟
Q:Serializable 是空接口,序列化机制是怎么被触发的?
A:ObjectOutputStream.writeObject 里会 instanceof 检查,非 Serializable 抛 NotSerializableException。写流程先写类元描述(ObjectStreamClass:类名、UID、字段签名),再按描述逐字段写值;读流程按元描述恢复。若类中定义了 private writeObject/readObject,会通过反射优先调用实现自定义逻辑(Externalizable 则是完全手动的另一套接口,要求显式写所有字段并提供 public 无参构造)。
Q:transient 和 static 的区别?为什么 static 不需要 transient?
A:两者都不参与序列化,但语义不同——transient 是"这个实例字段不持久化"(敏感信息、可推导的缓存字段、不可序列化的资源句柄如流/连接);static 属于类,序列化本来就只针对对象状态,static 字段天然不在对象图里,反序列化后读到的是当前 JVM 里该类的 static 值。
Q:反序列化漏洞的原理和防御?
A:readObject 不只是"填字段",会执行类路径上相关类的读逻辑(readObject/readResolve/代理构造等)。攻击者把精心挑选的"利用链"(如 Commons-Collections 的 Transformer 链)序列化后投给服务端,服务端反序列化即触发任意代码执行。防御:① 禁止反序列化不可信来源的数据;② 生产环境弃用 Java 原生序列化,用 JSON/Protobuf;③ 必须用时配 ObjectInputFilter 白名单(JEP 290)限制可恢复的类型;④ 升级依赖,封堵已知 Gadget。