What actually causes a Stack Overflow error?

This question already has an answer here:

  • What is a StackOverflowError? 13 answers

  • It seems you're thinking that a stackoverflow error is like a buffer overflow exception in native programs, when there is a risk of writing into memory that had not been allocated for the buffer, and thus to corrupt some other memory locations. It's not the case at all.

    JVM has a given memory allocated for each stack of each thread, and if an attempt to call a method happens to fill this memory, JVM throws an error. Just like it would do if you were trying to write at index N of an array of length N. No memory corruption can happen. The stack can not write into the heap.

    A StackOverflowError is to the stack what an OutOfMemoryError is to the heap: it simply signals that there is no more memory available.

    Description from Virtual Machine Errors (§6.3)

    StackOverflowError : The Java Virtual Machine implementation has run out of stack space for a thread, typically because the thread is doing an unbounded number of recursive invocations as a result of a fault in the executing program.


    Aren't there other ways for a stack overflow to occur, not only through recursion?

    Sure. Just keep calling methods, without ever returning. You'll need a lot of methods, though, unless you allow recursion. Actually, it doesn't make a difference: a stack frame is a stack frame, whether it is one of a recursive method or not is the same.

    The answer to your second question is: The stackoverflow is detected when the JVM tries to allocate the stack frame for the next call, and finds it is not possible. So, nothing will be overwritten.


    Aren't there other ways for a stack overflow to occur, not only through recursion?

    Challenge accepted :) StackOverflowError without recursion (challenge failed, see comments):

    public class Test
    {
        final static int CALLS = 710;
    
        public static void main(String[] args)
        {
            final Functor[] functors = new Functor[CALLS];
            for (int i = 0; i < CALLS; i++)
            {
                final int finalInt = i;
                functors[i] = new Functor()
                {
                    @Override
                    public void fun()
                    {
                        System.out.print(finalInt + " ");
                        if (finalInt != CALLS - 1)
                        {
                            functors[finalInt + 1].fun();
                        }
                    }
                };
            }
            // Let's get ready to ruuuuuuumble!
            functors[0].fun(); // Sorry, couldn't resist to not comment in such moment. 
        }
    
        interface Functor
        {
            void fun();
        }
    }
    

    Compile with standard javac Test.java and run with java -Xss104k Test 2> out . After that, more out will tell you:

    Exception in thread "main" java.lang.StackOverflowError
    

    Second try.

    Now the idea is even simpler. Primitives in Java can be stored on the stack. So, let's declare a lot of doubles, like double a1,a2,a3... . This script can write, compile and run the code for us:

    #!/bin/sh
    
    VARIABLES=4000
    NAME=Test
    FILE=$NAME.java
    SOURCE="public class $NAME{public static void main(String[] args){double "
    for i in $(seq 1 $VARIABLES);
    do
        SOURCE=$SOURCE"a$i,"
    done
    SOURCE=$SOURCE"b=0;System.out.println(b);}}"
    echo $SOURCE > $FILE
    javac $FILE
    java -Xss104k $NAME
    

    And... I got something unexpected:

    #
    # A fatal error has been detected by the Java Runtime Environment:
    #
    #  SIGSEGV (0xb) at pc=0x00007f4822f9d501, pid=4988, tid=139947823249152
    #
    # JRE version: 6.0_27-b27
    # Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-amd64 compressed oops)
    # Derivative: IcedTea6 1.12.6
    # Distribution: Ubuntu 10.04.1 LTS, package 6b27-1.12.6-1ubuntu0.10.04.2
    # Problematic frame:
    # V  [libjvm.so+0x4ce501]  JavaThread::last_frame()+0xa1
    #
    # An error report file with more information is saved as:
    # /home/adam/Desktop/test/hs_err_pid4988.log
    #
    # If you would like to submit a bug report, please include
    # instructions how to reproduce the bug and visit:
    #   https://bugs.launchpad.net/ubuntu/+source/openjdk-6/
    #
    Aborted
    

    It's 100% repetitive. This is related to your second question:

    Does the StackOverflowError happen before the JVM actually overflows the stack or after?

    So, in case of OpenJDK 20.0-b12 we can see that JVM firstly exploded. But it seems like a bug, maybe someone can confirm that in comments please, because I'm not sure. Should I report this? Maybe it's already fixed in some newer version... According to JVM specification link (given by JB Nizet in a comment) JVM should throw a StackOverflowError , not die:

    If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.


    Third try.

    public class Test {
        Test test = new Test();
    
        public static void main(String[] args) {
            new Test();
        }
    }
    

    We want to create new Test object. So, its (implicit) constructor will be called. But, just before that, all the members of Test are initialized. So, Test test = new Test() is executed first...

    We want to create new Test object...

    Update: Bad luck, this is recursion, I asked question about that here.

    链接地址: http://www.djcxy.com/p/14126.html

    上一篇: 用堆而不是堆栈在C中分配二维数组

    下一篇: 什么实际上导致堆栈溢出错误?