java 对象互相引用 序列化 导致栈溢出例子

本文通过一个实例展示了在Java中,当两个实现了Serializable接口的对象互相引用时,进行序列化会导致栈溢出错误。这个问题是由于Jackson在序列化过程中陷入了无限递归。解决方法是在相互引用的属性上使用@JsonIgnore注解,避免在序列化时处理该字段,从而消除循环引用引发的错误。

在Java对象中如果A,B都实现了Serializable接口 并且互相引用 在进行序列化的时候 会出现循环引用导致栈溢出的问题

例子

新建类A

public class A implements Serializable {
    private int a;
    private String b;
    private B bb;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

    public B getBb() {
        return bb;
    }

    public void setBb(B bb) {
        this.bb = bb;
    }

}

新建类B

public class B implements Serializable {
    private int c;
    private String d;

    private A a;
    public int getC() {
        return c;
    }

    public void setC(int c) {
        this.c = c;
    }

    public String getD() {
        return d;
    }

    public void setD(String d) {
        this.d = d;
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

A和B互相引用 此处用的jackson进行序列化
新建SerializableTest

public class SerializableTest {
    public static void main(String[] args) throws Exception{
        A a = new A();
        B b = new B();

        a.setA(1);
        a.setB("b");
        a.setBb(b);


        b.setC(2);
        b.setD("d");
        b.setA(a);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
        oos.writeObject(a);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
        A aa = (A)ois.readObject();;

        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(aa);
        System.out.println(s);

    }
}

运行结果

	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:722)
Caused by: java.lang.StackOverflowError
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:740)
	... 1011 more
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844

在实际项目开发 可以采用
@JsonIgnore注解 在成员属性上加上即可忽略序列化 从而不会出现这样的问题

<think>我们正在处理Java集合的反序列化问题。根据用户提供的引用,我们可以总结出以下几点关键信息: 1. 引用[1]展示了一个自定义反序列化例子,通过`readObject`方法手动读取数据并重建集合对象。 2. 引用[2]提到了将集合序列化为JSON字符串的工具方法,但用户的问题是关于反序列化,所以我们可能需要关注如何从JSON反序列化回集合。 3. 引用[3]则展示了在序列化和反序列化过程中自定义处理数据的例子,并提出了一个常见问题:哪些对象可以被序列化? 用户的问题集中在Java集合的反序列化方法及常见问题上。因此,我们将围绕如何正确反序列化Java集合以及可能遇到的问题展开回答。 ### Java集合的反序列化Java中,集合的反序列化通常指将序列化后的字节流(或JSON等格式的字符串)转换回集合对象的过程。根据序列化方式的不同,反序列化方法也有所区别。这里我们主要讨论两种常见情况:Java原生序列化和JSON序列化。 #### 1. Java原生序列化 Java原生序列化使用`ObjectOutputStream`和`ObjectInputStream`。要使一个类的对象序列化,该类必须实现`java.io.Serializable`接口。集合类(如`ArrayList`, `HashSet`等)已经实现了该接口,因此可以直接序列化和反序列化。 **基本反序列化方法:** ```java try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.ser"))) { List<String> list = (List<String>) ois.readObject(); } ``` **自定义反序列化:** 有时我们需要在反序列化过程中进行一些自定义操作(如解密、数据校验等)。这时可以在集合所在的类中定义`readObject`方法。例如,引用[1]中展示了如何自定义反序列化一个包含字符串列表的类: ```java private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { int size = ois.readInt(); names = new ArrayList<>(size); for (int i = 0; i < size; i++) { names.add((String) ois.readObject()); } } ``` #### 2. JSON序列化与反序列化 在实际应用中,JSON格式更为常见。我们可以使用如Jackson、Gson等库来实现集合与JSON字符串之间的转换。 以Jackson库为例,反序列化一个集合的步骤如下: 1. 创建`ObjectMapper`实例。 2. 使用`readValue`方法将JSON字符串转换为集合对象。由于Java泛型的类型擦除,我们需要使用`TypeReference`来指定集合的具体类型。 **示例:** ```java ObjectMapper objectMapper = new ObjectMapper(); String json = "[ \"apple\", \"banana\", \"cherry\" ]"; List<String> list = objectMapper.readValue(json, new TypeReference<List<String>>() {}); ``` ### 常见问题及解决方法 1. **哪些对象可以被序列化?** - 只有实现了`Serializable`接口的类的对象才能被Java原生序列化。集合类(如`ArrayList`, `HashMap`等)都实现了该接口。但是,如果集合中的元素没有实现`Serializable`,则序列化时会抛出`NotSerializableException`[^3]。 2. **序列化版本不一致问题(`InvalidClassException`)** - 如果序列化后修改了类的结构(如增加了字段),反序列化时可能会因序列版本UID(`serialVersionUID`)不匹配而失败。解决方法是在类中显式声明一个固定的`serialVersionUID`: ```java private static final long serialVersionUID = 1L; ``` 3. **反序列化安全风险** - 反序列化不受信任的数据可能导致安全问题,如远程代码执行(RCE)。避免反序列化来自不可信源的数据。如果必须这样做,可以考虑使用白名单验证对象类型,或者使用JSON等更安全的序列化格式。 4. **集合元素为自定义对象** - 当集合中的元素是自定义类时,该类也必须实现`Serializable`接口(对于Java原生序列化)。对于JSON反序列化,自定义类不需要实现`Serializable`,但需要有默认构造器(或能通过其他方式构造),并且字段需要与JSON属性匹配(或通过注解配置)。 5. **性能问题** - 对于大型集合,反序列化可能消耗较多内存和时间。使用JSON时,可以尝试流式解析(如Jackson的`JsonParser`)来减少内存占用。 6. **循环引用问题** - 当集合中的对象存在循环引用时,使用某些序列化库(如Jackson默认配置)可能会导致栈溢出。可以通过配置忽略循环引用(如`@JsonIgnoreProperties`)或使用`@JsonManagedReference`和`@JsonBackReference`解决。 ### 总结 Java集合的反序列化可以通过Java原生序列化或JSON序列化库实现。在反序列化过程中,需要注意对象的可序列化性、版本兼容性、安全性以及性能等问题。根据具体需求选择合适的序列化方式,并采取相应的安全措施和性能优化手段。 §§相关问题§§ 1. 如何解决Java序列化中的版本不一致问题? 2. 使用JSON反序列化集合时,如何处理泛型类型? 3. 如何避免Java序列化安全漏洞? 4. 在反序列化大型集合时,如何优化内存使用?</think>### Java集合反序列化方法及常见问题 Java集合的反序列化是将序列化的字节流恢复为内存中的集合对象的过程。根据引用内容,以下是核心方法和常见问题: #### 一、反序列化方法 1. **Java原生反序列化** ```java try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.ser"))) { List<String> list = (List<String>) ois.readObject(); // 强制类型转换 } ``` - 要求集合元素必须实现`Serializable`接口 - 使用`readObject()`方法读取对象[^3] 2. **自定义反序列化(覆盖readObject)** ```java private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { int size = ois.readInt(); // 读取集合大小 names = new ArrayList<>(size); for (int i = 0; i < size; i++) { names.add((String) ois.readObject()); // 逐个读取元素 } } ``` - 适用于需要特殊处理(如解密、校验)的场景[^1] 3. **JSON反序列化(使用Jackson库)** ```java ObjectMapper mapper = new ObjectMapper(); List<User> users = mapper.readValue(jsonString, new TypeReference<List<User>>(){}); ``` - 需处理`JsonProcessingException`[^2] - 要求集合元素有无参构造器 #### 二、常见问题及解决方案 1. **类型转换异常(ClassCastException)** - **原因**:序列化与反序列化的类版本不一致 - **解决**: ```java private static final long serialVersionUID = 1L; // 显式声明版本ID ``` 2. **元素不可序列化(NotSerializableException)** - **原因**:集合元素未实现`Serializable` - **示例**:`List<NonSerializableObj>`会抛出异常 - **解决**:确保所有元素实现`Serializable`[^3] 3. **安全漏洞(反序列化攻击)** - **风险**:恶意字节流可执行任意代码 - **防护**: ```java ois.setObjectInputFilter( // JDK 9+ filterInfo -> Class.class.equals(filterInfo.serialClass()) ? ObjectInputFilter.Status.REJECTED : ObjectInputFilter.Status.UNDECIDED ); ``` 4. **JSON反序列化泛型擦除** - **问题**:`List<User>`运行时变为`List<Object>` - **解决**:使用`TypeReference`保留泛型信息[^2] ```java mapper.readValue(json, new TypeReference<List<User>>(){}); ``` 5. **循环引用问题** - **现象**:对象间相互引用导致栈溢出 - **解决**:JSON中使用`@JsonIgnoreProperties(ignoreUnknown=true)` #### 三、最佳实践 1. **敏感字段处理**: ```java private transient String password; // 不被序列化 ``` 2. **大型集合优化**: - 分块序列化(如每10,000元素存一个文件) - 使用`ArrayList`而非`LinkedList`(内存连续) 3. **版本兼容**: - 修改类结构后需保持`serialVersionUID`一致 4. **数据校验**: ```java private void readObject(ObjectInputStream ois) ... { // 反序列化后执行校验 if(size < 0) throw new InvalidObjectException("非法大小"); } ``` > **关键点**:集合反序列化本质是递归重建对象树的过程,需确保对象图的完整性和一致性[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值