Java原生序列化天然支持循环引用,通过句柄表为对象分配唯一ID并写入TC_REFERENCE标记,反序列化时完整还原引用关系;JSON库如Jackson需显式启用引用跟踪,深克隆须确保对象图全可序列化。

Java 原生序列化(java.io.Serializable)本身就能安全处理循环引用,无需额外干预——它靠内置句柄表自动识别重复对象并写入引用标记,不是靠“限制递归深度”或“跳过字段”。真正出问题的,往往是用错了工具或绕开了机制。
原生序列化天然支持循环引用
ObjectOutputStream 在序列化时会为每个首次出现的对象分配唯一句柄,并记录在内部句柄表中。当再次遇到同一对象(比如 A→B→A),不会重复展开字段,而是写入 TC_REFERENCE 标记加已有 ID。反序列化时,句柄表同步重建,原始引用关系(包括循环)被完整还原。
- 只要类实现
Serializable,字段不带transient或不可序列化类型,整个过程完全透明 - 不需要注解、不用配置、不改字段,连
serialVersionUID都非强制(但建议显式声明) - 示例中两个
Node互相设parent,调用writeObject(a)正常完成,不会栈溢出
JSON 库才是循环引用高发区
Jackson、Fastjson、Gson 默认按树形遍历对象,不启用引用跟踪时会无限递归。这不是序列化机制的问题,而是 JSON 库默认策略使然。
- Jackson:加
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class),或全局开启enableDefaultTyping() - Fastjson:1.2.83+ 默认支持
$ref;旧版本需设SerializerFeature.DisableCircularReferenceDetect = false - Gson:默认不支持,可用
GraphAdapterBuilder将对象图转为 ID 映射结构,避免嵌套
深克隆场景要防手动踩坑
用序列化做深克隆(如字节数组 → 反序列化)虽可行,但必须确保整个对象图可序列化:所有类实现 Serializable,避免非静态内部类、Lambda、线程局部变量等隐式持外层引用。
立即学习“Java免费学习笔记(深入)”;
- 非静态内部类会悄悄持有外部类引用,导致本不该克隆的对象也被拉进来
- 集合字段若用懒加载(如 Hibernate 代理),可能触发初始化失败或意外数据库访问
- 推荐改用
static嵌套类,或把节点类定义为顶层类,明确边界
调试与验证技巧
遇到栈溢出别急着改逻辑,先确认是不是真由循环引用引发:
- 打印关键对象的
System.identityHashCode(obj),看是否反复出现相同值 - 手动顺着引用链走两三级:比如
a.parent.children.get(0).parent是否能回到a - 用
objgraph(Python)或 JVM 自带的jmap -histo+ 堆转储分析引用路径


















