是否可以在没有递归的情况下获得StackOverflowError?

时间:2018-03-02 20:11:56

标签: java stack-overflow

我有一个任务得到" StackOverflowError"在java中没有使用-Xss和递归。我真的没有想法......只有一些废话,比如在运行时生成巨大的java类,编译它并调用...

6 个答案:

答案 0 :(得分:3)

编辑:

答案是不正确的,因为它是一种递归。它被称为间接递归https://en.wikipedia.org/wiki/Recursion_(computer_science)#Indirect_recursion

我认为没有递归的最简单方法是:

import java.util.LinkedList;
import java.util.List;

interface Handler {
    void handle(Chain chain);
}

interface Chain {
    void process();
}

class FirstHandler implements Handler {
    @Override
    public void handle(Chain chain) {
        System.out.println("first handler");
        chain.process();
    }
}

class SecondHandler implements Handler {
    @Override
    public void handle(Chain chain) {
        System.out.println("second handler");
        chain.process();
    }
}

class Runner implements Chain {
    private List<Handler> handlers;
    private int size = 5000; // change this parameter to avoid stackoverflowerror
    private int n = 0;

    public static void main(String[] args) {
        Runner runner = new Runner();
        runner.setHandlers();
        runner.process();
    }

    private void setHandlers() {
        handlers = new LinkedList<>();
        int i = 0;
        while (i < size) {
            // there can be different implementations of handler interface
            handlers.add(new FirstHandler());
            handlers.add(new SecondHandler());
            i += 2;
        }
    }

    public void process() {
        if (n < size) {
            Handler handler = handlers.get(n++);
            handler.handle(this);
        }
    }
}

乍一看这个例子看起来有点疯狂,但它并不像看起来那么不现实。

这种方法的主要思想是责任链模式。您可以通过实施责任链模式在现实生活中重现此异常。例如,在做了一些逻辑调用链中的下一个对象之后,你有一些对象和每个对象,并将他的工作结果传递给下一个。

您可以在java过滤器(javax.servlet.Filter)中看到这一点。 我不知道使用这个类的详细机制,但它使用doFilter方法调用链中的下一个过滤器,并且在所有过滤器/ servlet处理请求之后,它继续在doFilter下面的相同方法中工作。

换句话说,它在servlet之前和向客户端发送响应之前拦截请求/响应。这是一段危险的代码,因为所有被调用的方法都位于同一个线程的同一堆栈中。因此,如果链太大或者您在深层调用doFilter方法也会提供相同的情况,它可能会启动stackoverflow异常。也许,在调试期间,您可能会看到一连串的调用  在一个线程中,它可能是stackoverflower的原因。

此外,您可以从下面的链接中获取责任链模式示例,并添加元素集合而不是几个,您也将获得stackoverflower错误。

与模式的链接:

https://www.journaldev.com/1617/chain-of-responsibility-design-pattern-in-java https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern

我希望它对你有所帮助。

答案 1 :(得分:2)

Java在栈上存储原始类型。在本地作用域中创建的对象在堆上分配,并在堆栈上引用它们。

通过在方法范围中分配太多基本类型,可以在不递归的情况下溢出堆栈。使用正常的堆栈大小设置,您必须分配过多的变量才能溢出。

答案 2 :(得分:2)

由于这个问题非常有趣,我试图简化 hide 的答案:

public class Stackoverflow {

    static class Handler {
        void handle(Chain chain){
            chain.process();
            System.out.println("yeah");
        }
    }

    static class Chain {

        private List<Handler> handlers = new ArrayList<>();
        private int           n        = 0;

        private void setHandlers(int count) {
            int i = 0;
            while (i++ < count) {
                handlers.add(new Handler());
            }
        }

        public void process() {
            if (n < handlers.size()) {
                Handler handler = handlers.get(n++);
                handler.handle(this);
            }
        }
    }

    public static void main(String[] args) {
        Chain chain = new Chain();
        chain.setHandlers(10000);
        chain.process();
    }
}

重要的是要注意,如果发生stackoverflow,将永远不会输出字符串“yeah”。

答案 3 :(得分:1)

这是Eric J.想法的实现,该想法使用javassist库生成过多的局部变量:

class SoeNonRecursive {
    static final String generatedMethodName = "holderForVariablesMethod";

    @SneakyThrows
    Class<?> createClassWithLotsOfLocalVars(String generatedClassName, final int numberOfLocalVarsToGenerate) {
        ClassPool pool = ClassPool.getDefault();
        CtClass generatedClass = pool.makeClass(generatedClassName);
        CtMethod generatedMethod = CtNewMethod.make(getMethodBody(numberOfLocalVarsToGenerate), generatedClass);
        generatedClass.addMethod(generatedMethod);
        return generatedClass.toClass();
    }

    private String getMethodBody(final int numberOfLocalVarsToGenerate) {
        StringBuilder methodBody = new StringBuilder("public static long ")
                .append(generatedMethodName).append("() {")
                .append(System.lineSeparator());
        StringBuilder antiDeadCodeEliminationString = new StringBuilder("long result = i0");
        long i = 0;
        while (i < numberOfLocalVarsToGenerate) {
            methodBody.append("  long i").append(i)
                      .append(" = ").append(i).append(";")
                      .append(System.lineSeparator());
            antiDeadCodeEliminationString.append("+").append("i").append(i);
            i++;
        }
        antiDeadCodeEliminationString.append(";");
        methodBody.append("  ").append(antiDeadCodeEliminationString)
                  .append(System.lineSeparator())
                  .append("  return result;")
                  .append(System.lineSeparator())
                  .append("}");
        return methodBody.toString();
    }
}

和测试:

class SoeNonRecursiveTest {
    private final SoeNonRecursive soeNonRecursive = new SoeNonRecursive();
    //Should be different for every case, or once generated class become
    //"frozen" for javassist: http://www.javassist.org/tutorial/tutorial.html#read
    private String generatedClassName;

    @Test
    void stackOverflowWithoutRecursion() {
        generatedClassName = "Soe1";
        final int numberOfLocalVarsToGenerate = 6000;
        assertThrows(StackOverflowError.class, () -> soeNonRecursive
                .createClassWithLotsOfLocalVars(generatedClassName, numberOfLocalVarsToGenerate));
    }

    @SneakyThrows
    @Test
    void methodGeneratedCorrectly() {
        generatedClassName = "Soe2";
        final int numberOfLocalVarsToGenerate = 6;
        Class<?> generated = soeNonRecursive.createClassWithLotsOfLocalVars(generatedClassName, numberOfLocalVarsToGenerate);
        //Arithmetic progression
        long expected = Math.round((numberOfLocalVarsToGenerate - 1.0)/2 * numberOfLocalVarsToGenerate);
        long actual = (long) generated.getDeclaredMethod(generatedMethodName).invoke(generated);
        assertEquals(expected, actual);
    }
}

答案 4 :(得分:0)

我们当然可以做到:)。根本没有递归!

public static void main(String[] args) {
    throw new StackOverflowError();
}

答案 5 :(得分:-2)

看下面这个答案,不确定这是否适用于Java,但听起来你可以声明一个指针数组?可以在不需要发电机的情况下实现Eric J的想法。

Is it on the Stack or Heap?

int* x[LARGENUMBER];   // The addresses are held on the stack
int i;        // On the stack
for(i = 0; i < LARGENUMBER; ++i)
   x[i] = malloc(sizeof(int)*10);  // Allocates memory on the heap