是否有可能在不递归的情况下得到 StackOverflowError?

Is it possible to get StackOverflowError without recursion?

我的任务是在不使用 -Xss 和递归的情况下在 java 中获取 "WhosebugError"。我真的没有想法...只有一些废话,例如在运行时生成巨大的java class,编译并调用...

Java 在堆栈上存储基本类型。在本地范围内创建的对象分配在堆上,对它们的引用在堆栈上。

您可以通过在方法作用域中分配过多的基本类型来不递归地溢出堆栈。使用正常的堆栈大小设置,您将不得不分配过多的变量以溢出。

查看下面的答案,不确定这是否适用于 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

编辑:

答案不正确,因为它是一种递归。它被称为间接递归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 Whosebugerror
    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) 中看到它。 我不知道这个 class 的详细工作机制,但它使用 doFilter 方法调用链中的下一个过滤器,并且在所有 filters/servlets 处理请求之后,它继续在 doFilter 下面的相同方法中工作。

换句话说,它在 servlet 之前和向 client.It 发送响应之前拦截 request/response 是一段危险的代码,因为所有被调用的方法都在同一线程的同一堆栈中。因此,如果链太大或者您在深层调用 doFilter 方法也提供相同的情况,它可能会引发 Whosebug 异常。也许,在调试期间你可能会看到调用链 在一个线程中,它可能是 Whosebugerror 的原因。

您也可以从下面的链接中获取责任链模式示例并添加 元素集合 而不是几个,您也会得到 Whosebugerror。

与模式的链接:

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

希望对您有所帮助。

由于这个问题很有趣,所以我尝试简化隐藏的答案:

public class Whosebug {

    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();
    }
}

需要注意的是,如果发生Whosebug,字符串"yeah"永远不会输出

下面是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 WhosebugWithoutRecursion() {
        generatedClassName = "Soe1";
        final int numberOfLocalVarsToGenerate = 6000;
        assertThrows(WhosebugError.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);
    }
}

当然可以:)。完全没有递归!

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