在 Java 里,一个对象只要实现了 `java.io.Serializable` 接口,就能被 `ObjectOutputStream/ObjectInputStream` 直接序列化/反序列化,根本原因是:
1. 接口本身只是一个“标记”(Marker Interface),没有任何待实现的方法;
2. JVM 在运行期通过“类型检查”发现它打了这个标记,就会启用一套由 JDK 底层实现的默认序列化机制;
3. 这套机制借助反射和 Unsafe 能力,绕过了构造器、访问修饰符等限制,把对象的整个图结构(类元数据、字段值、类型信息、引用关系)直接写进字节流,读回时再现场重建对象和引用关系。
换句话说,实现 `Serializable` 只是给 JVM 发了一张“通行证”,真正干活的并不是接口,而是 JDK 里那套“看见通行证就自动触发”的 native 级实现。下面把关键步骤拆开讲。
---
一、标记接口:给 JVM 一个“开关”
- `Serializable` 接口体是空的,没有 `writeObject/readObject` 方法需要实现。
- 运行期 `ObjectOutputStream` 会执行
`if (obj instanceof Serializable)`
只有返回 true 才继续,否则直接抛 `NotSerializableException`。
→ 它只是一个“运行期可见的布尔标志”,让序列化框架知道“这个类我敢写”。
---
二、默认序列化:JDK 的“黑盒”实现
当 `writeObject(obj)` 被调用时,JDK 在后台完成以下动作(简化版):
1. 拿到对象所属的 `Class<?>`,通过反射遍历其整个继承链(直到 `Object`)。
2. 对每一个可序列化类,收集它的
- 类全名(`java.lang.String` 等)
- `serialVersionUID`(显式或计算)
- 所有非 transient 非 static 字段名、类型、值
3. 如果字段是原始类型,直接按大端写值;
如果是对象引用,递归地重复 13,直到把整个对象图展开成一棵“有向无环图”。
4. 写流格式:
`0x73` (TC_OBJECT)
→ 类描述符 (`TC_CLASSDESC`)
→ 字段值块 (`TC_BLOCKDATA`)
→ 引用句柄 (`TC_REFERENCE`) …
这是 JVM 定义的私有协议,与 class 文件格式类似,但专为对象存储设计。
5. 读回时,先读类描述符,再用 Unsafe.allocateInstance(Class) 直接绕过构造器生成空对象,然后把字段值按顺序“回填”进去,最后重建引用关系。
---
三、反射 + Unsafe:为什么“ private 字段也能动”
- 正常代码无法给 `private int x` 赋值,但 `ObjectStreamClass` 在初始化时会为每个字段生成一个 `FieldReflector`,底层调用
`sun.misc.Unsafe.putInt(Object o, long offset, int x)`
这条 native 指令可以无视访问控制,直接写内存。
- 因此,即使字段全是 `private`、类没有 `setter`,序列化框架依旧可以“把内存原样搬走,再原样搬回来”。
---
四、显式自定义:什么时候需要 `writeObject/readObject`
- 如果类里写了
`private void writeObject(ObjectOutputStream out) throws IOException`
框架就会反射调用它,把控制权交给程序员;
否则就走上面的默认算法。
- 同理,`readObject` 允许你在反序列化完字段后再做校验、防御性拷贝、缓存重建等额外逻辑。
---
五、总结一句话
实现 `Serializable` 本身不做任何事,它只是让 JVM 的序列化框架“在运行期看见绿灯”;
绿灯一亮,JDK 就启动一套基于反射+Unsafe 的私有协议,把对象在内存中的完整状态“搬”到字节流里,再“搬”回来,于是看上去“实现了接口就能序列化”。
22万+

被折叠的 条评论
为什么被折叠?



