Java 深度图导致堆栈溢出:非递归序列化选项?

Java 深度图导致堆栈溢出:非递归序列化选项?,java,serialization,recursion,non-recursive,Java,Serialization,Recursion,Non Recursive,我们从Java的序列化库中获取StackOverflowers错误。问题在于,默认的序列化实现是递归的,其深度仅受通过引用网络的最长路径的限制 我们意识到我们可以覆盖默认方法,但是我们的项目中有数百个连接丰富的类,所以我们对覆盖方法不感兴趣。如果有一个非递归的通用解决方案(或者至少将递归从堆栈移动到堆),我们更感兴趣 我在谷歌上搜索了这个话题,发现只有很多人对同一件事怨声载道,但这些抱怨大多来自多年前。情况是否有所改善?如果没有,我们写一个通用的实现,你有什么建议吗?我们认为没有人破解这个坚果是

我们从Java的序列化库中获取StackOverflowers错误。问题在于,默认的序列化实现是递归的,其深度仅受通过引用网络的最长路径的限制

我们意识到我们可以覆盖默认方法,但是我们的项目中有数百个连接丰富的类,所以我们对覆盖方法不感兴趣。如果有一个非递归的通用解决方案(或者至少将递归从堆栈移动到堆),我们更感兴趣


我在谷歌上搜索了这个话题,发现只有很多人对同一件事怨声载道,但这些抱怨大多来自多年前。情况是否有所改善?如果没有,我们写一个通用的实现,你有什么建议吗?我们认为没有人破解这个坚果是有原因的(我们还不清楚)。从理论上讲,做得“正确”听起来应该是可行的。

看来我没有很好地理解这个问题。您似乎对序列化可能包含循环引用的属性感兴趣。如果这个假设是错误的,您可以不序列化这些包含循环引用的对象,请参考下面我的原始答案

新答案

我认为您需要跟踪哪些对象已被序列化,我无法看到这种情况发生,除非您自己这样做。不过应该不会太难

在这些包含循环引用的对象上,可以保留一个表示对象是否已序列化的
瞬态布尔值。然后,您必须重写默认的序列化行为,但这只需几行即可完成

public void writeExternal(ObjectOutput out) {
    if(!out.serialized) {
        out.serializeMethod();
    }
    out.serialized = true;
}
原始答案

看一看

我可以想象,大多数序列化库都会使用
transient
关键字。如果成员是瞬态的,则意味着从序列化中排除该成员

class Something {
    private Dog dog; // I will be serialized upon serialization.
    private transient SomethingElse somethingElse; // I will not be serialized upon serialization.
}

class SomethingElse {
    private Cat cat; // I will be serialized upon serialization.
    private transient Something something; // I will not be serialized upon serialization.
}

如果您有类似于上述场景的递归成员,您可能希望将其中一个(或两者)标记为
瞬态
,以便不会发生此溢出。

证明JDK 6序列化可以处理递归对象图:

public static void main(String[] args) throws Exception {
    Foo foo = new Foo("bob");
    foo.setBar(new Bar("fred", foo));
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(baos);
    out.writeObject(foo);
    out.close();
    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
    Object o = in.readObject();
    System.out.println(o);
}

static class Foo implements Serializable {
    String name;
    Bar bar;

    Foo(String name) {
        this.name = name;
    }

    void setBar(Bar bar) {
        this.bar = bar;
    }

    @Override
    public String toString() {
        return "Foo{" +
                "name='" + name + '\'' +
                ", bar=" + bar +
                '}';
    }
}

static class Bar implements Serializable {
    String name;
    Foo foo;

    Bar(String name, Foo foo) {
        this.name = name;
        this.foo = foo;
    }

    @Override
    public String toString() {
        return "Bar{" +
                "name='" + name + '\'' +
                '}';
    }
}
输出:

Foo{name='bob', bar=Bar{name='fred'}}

不久前我就有这个问题。对于连接丰富的类,即使您能够在没有堆栈溢出的情况下完成序列化,序列化也非常缓慢。当我们解决这个问题时,我们有几个类,所以我们只是创建了自己的序列化格式,将数据打包成一组整数对象id,每个字段都有整数字段id,并通过一系列对象id、字段id和其他对象id映射来描述它们的连接。这种定制方法速度非常快,占用内存也非常少,但实际上只有在您有一小部分要序列化的类时才有效

一般情况下要困难得多,对连接丰富的类进行序列化的需求也没有那么强烈,所以我想这就是为什么没有人解决这个问题

不过,您基本上已经解决了这个问题,您将始终需要一个堆栈深度等于深度优先搜索树的最大高度,因此,每当图形深度超过该深度时,您将得到堆栈溢出。这从根本上说是一个递归问题,因此您需要使用递归或通过将堆栈分配移动到放置在堆上的堆栈对象来伪造递归。我将看一看OpenJDK实现:

您已经有了DebugTraceInfo堆栈,我将为您正在写入的当前对象创建第二个堆栈字段,并更改WriteObject方法以将对象推送到堆栈上,如下所示:

stack.push(obj);
while(!stack.empty()) {
    obj = stack.pop();
    ...
然后您只需将所有调用更改为WriteObject 0(x);堆叠。推(x);。递归和迭代之间的简单、标准转换,除了类将近2500行和可能有大量的gotchas之外


如果您最终构建了它,我建议将at作为补丁提交给下一版本的java,因为它会很有用,类似于用于深层对象图的IterativeObjectOutputStream。

GWT RPC序列化基本上等同于JVM序列化,两者都使用堆栈/递归技术。不幸的是,将工作分块(如果您在浏览器中工作,即使用GWT,则需要执行此操作),这不适用于将工作分块,因此这里有一种非递归方法:

本质上,将序列化转换为三个过程: *实例化对象 *设置属性(通过链接) *填充集合

两个技巧:一些对象在实例化时需要属性(例如日期),而您需要最后填充集合,因为它们可能需要其成员的哈希

这允许非递归反序列化-但事实上,非递归序列化更简单(只要没有自定义writeReplace/readResolve),只需在writeObject中维护两个未序列化对象队列,未序列化当前对象的属性,并且已将serialize object属性使用标记推送到堆栈,而不是进行递归调用

这里有一个非常基本的例子:

我想知道序列化程序将如何处理循环引用?我猜是无限循环导致堆栈溢出。听起来你有很多紧密耦合的代码。糟糕的juju。这真的是“太长”(但长度固定)路径的问题吗?还是因为路径从未终止而发生的问题?堆栈可以包含许多帧,因此我怀疑是后者。如果是后者,则必须使用知道如何处理循环的序列化程序(通过修剪或在序列化数据中添加“链接”)。此外,还可以增加堆栈大小(同样,只有在对象图实际终止时才有用):请参阅(另一方面,使用stan