【问题标题】:Serializing Java Object without StackOverflowError在没有 StackOverflowError 的情况下序列化 Java 对象
【发布时间】:2014-08-05 20:17:59
【问题描述】:

我有一个相当大的 Java 对象,它在内存中表示一个带有顶点和边的图形。每个顶点都有一个ArrayList 连接到它的其他顶点(并且有一个HashMap 数据结构以及用于其他目的)。该图可以有几千个顶点,以及更多的边。

当尝试使用 Java 的内置序列化(implements Serializable 等)对图形进行序列化时,我总是遇到StackOverflowError。将图形的其他属性设置为 transient 没有帮助,将堆栈大小设置为更大也没有帮助(即 -Xss1g 或 -Xss512m)。

我认为我不需要自定义writeObject 方法,因为ArrayList 和HashMap 已经有它们自己的实现,它们在序列化时被调用。

我的问题是:有没有办法在不获取StackOverflowError 的情况下序列化已经在内存中的大型 Java 对象?

编辑:这是堆栈跟踪:

Exception in thread "main" java.lang.StackOverflowError
at java.lang.reflect.Method.invoke(Method.java:575)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:950)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1482)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1159)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:329)
at java.util.ArrayList.writeObject(ArrayList.java:570)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
// Many more lines after this

这是我的Graph 课程的概述:

public class Graph implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = -2632163054149021990L;
private ArrayList<Vertex> vertices;

private HashMap<Integer, Set<Vertex>> map;

public Graph(int rowMax, int colMax)
{
    map = new HashMap<Integer, Set<Vertex>>();

    this.vertices = new ArrayList<Vertex>();
}

public void connectVertices(Vertex u, Vertex v)
{
    u.addNeighbor(v);
    v.addNeighbor(u);
}

// other unrelated methods after this

这是我的Vertex 课程:

public class Vertex implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = 8520500010710631610L;
public int row;
public int col;
private ArrayList<Vertex> neighbors; // may change this to Set<Vertex>

public Vertex(int i, int j)
{
    this.row = i;
    this.col = j;
    this.neighbors = new ArrayList<Vertex>();
}

public boolean addNeighbor(Vertex v)
{
    this.neighbors.add(v);
    return true;
}

// unrelated methods after this

编辑 2:此外,对于较小尺寸但具有“邻居”的图形,不存在此问题。

【问题讨论】:

  • 关于什么的递归?此外,编写一个保证在该方法中不递归的自定义 writeObject 方法不会改变任何内容。
  • 会不会是你在某处有循环引用?如果它已经在内存中,我认为问题不在于对象大小。
  • 这可能是真的,我不知道会发生这种情况。不过,我确实为顶点生成了serialVersionUID。我该如何避免这个问题?
  • 伙计们,对已经序列化的对象进行序列化不会导致堆栈溢出。 Java 序列化句柄而不是对象。循环对象图可以序列化。你们都在叫错树。 @OP您将不得不发布一些代码。你有自定义的 writeObject() 方法吗?
  • 我当然会将邻居列表更改为一个集合。这将大大减少数据大小。

标签: java serialization stack-overflow


【解决方案1】:

如果图形的深度对于默认序列化处理而言太大,序列化将抛出 StackOverflowError。这是由于默认序列化在解析您的图形时递归地序列化每个节点。

平面结构可以正常工作(例如,具有 2000 个子节点的父节点),但深层结构会失败(例如,具有 2000 个后代级别的节点)。

例如以下将堆栈溢出:

public class Node implements Serializable
{
    private ArrayList<Node> nodes = new ArrayList<Node>();

    public static void main(String[] args) throws Exception
    {
        Node node = new Node();
        int depth = 3000;

        // Add nodes chained down to specified depth
        Node last = node;
        for (int i = 0; i < depth; i++)
        {
            Node temp = new Node();
            last.nodes.add(temp);
            last = temp;
        }

        System.out.println("starting");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);
        // Below line will cause a stack overflow.
        out.writeObject(node);

        System.out.println("done");
    }
}

您需要减少图形的深度以限制递归序列化调用的数量,或者编写自定义序列化来解决此问题。自定义序列化本质上需要是非递归的,不幸的是,乍一看似乎并不容易实现。

【讨论】:

  • 感谢您的回答。什么是自定义序列化,以便我只需在 ObjectInputStream 上调用 readObject?
  • 自定义序列化涉及在要序列化的对象上实现writeObject、readObject和readObjectNoData。只要实现了这些方法,那么您就可以在 ObjectInputStream 上使用 readObject,问题是您这里需要的实现至少有些复杂。
【解决方案2】:

我遇到了类似的问题。经过多次狩猎,我发现了一个 Kryo 的叉子,用于处理深度嵌套的对象。通过 https://github.com/EsotericSoftware/kryo/issues/103 ,克隆和 mvn clean install https://github.com/romix/kryo/tree/kryo-2.23-continuations 。目前是com.esotericsoftware.kryo:kryo:2.23-SNAPSHOT。

(旁注:有很多关于 SO 的问题可以用这个来回答。我应该发布答案的副本(如https://stackoverflow.com/a/43327778/513038),还是评论这个答案的链接,或者将问题标记为重复指向这个,还是什么?)

【讨论】:

    【解决方案3】:

    我知道这已经晚了好几年,但我在尝试自己解决问题时通过 google 发现了这一点。

    我想要一个比编写自定义序列化代码所需代码量少得多的解决方案。

    问题是节点连接到节点连接到节点。 网络的很大一部分都可以从任何给定节点到达 - 对于无向图,它是每个节点。

    当您尝试序列化一个节点时,每个可访问的节点都将在堆栈中结束。

    最简单的解决方案不是将一个节点直接连接到它的邻居,而是添加一个间接层:

    class Node{
    
        private final Map<NodeRef, Connection> forwardConnections = new HashMap<>();
        private final Map<NodeRef, Connection> reverseConnections = new HashMap<>();
    
        ...
    }
    
    class Connection{
    
        private final NodeRef source;
        private final NodeRef dest;
        private final ConnectionMetadata meta;
    
        public Connection(Node source, Node dest, ConnectionMetadata meta){
            this.source = new NodeRef(source);
            this.dest = new NodeRef(dest);
        }
    
        public Node getSource(){
            return source.resolve();
        }
    
        public Node getDest(){
            return dest.resolve();
        }
    
        public ConnectionMetadata getMeta(){
            return meta;
        }
    
    }
    
    public class NodeRef{
    
        private transient Node node;
        private final Network network;
        private final int[] uid;
    
        public NodeRef(Node node){
            this.node = node;
            this.network = node.getNetwork();
            this.uid = node.getUID();
        }
    
        //Won't be called during deserialisation
        public Node resolve(){
            if(node == null){
                node = network.resolve(uid);
            }
            return node;
        }
    
        //Will be called when connections maps are deserialised.
        public boolean equals(Object o){
            //you might also want to check that the networks are equal
            return (o instanceof NodeRef) && Arrays.equals(uid, ((NodeRef)o).uid);
        }
    
    
        //Will be called when connections maps are deserialised.
        public int hashCode(){
            return Arrays.hashCode(uid);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-14
      • 1970-01-01
      • 2015-02-06
      • 2018-08-26
      • 2010-10-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多