Wat is een StackOverflowError?

Wat is een StackOverflowError, wat is de oorzaak en hoe moet ik ermee omgaan?


Antwoord 1, autoriteit 100%

Parameters en lokale variabelen worden toegewezen op de stack (bij referentietypes leeft het object op de heap en een variabele in de stack verwijst naar dat object op de heap) . De stapel bevindt zich meestal aan de bovenste kant van uw adresruimte en als deze opgebruikt is, gaat deze naar de onderkant van de adresruimte (dwz richting nul).

Uw proces heeft ook een hoop, die zich aan de onderkant van uw proces bevindt. Naarmate u geheugen toewijst, kan deze heap naar de bovenkant van uw adresruimte groeien. Zoals je kunt zien, bestaat de kans dat de hoop “botst” met de stapel (een beetje zoals tektonische platen!!!).

De meest voorkomende oorzaak van een stack-overflow is een slecht recursieve aanroep. Dit wordt meestal veroorzaakt wanneer uw recursieve functies niet de juiste beëindigingsvoorwaarde hebben, zodat het zichzelf voor altijd aanroept. Of wanneer de beëindigingsvoorwaarde in orde is, kan dit worden veroorzaakt doordat er te veel recursieve oproepen nodig zijn voordat eraan wordt voldaan.

Met GUI-programmering is het echter mogelijk om indirecte recursie te genereren. Uw app kan bijvoorbeeld paint-berichten verwerken en tijdens het verwerken ervan kan het een functie aanroepen die ervoor zorgt dat het systeem een ​​ander paint-bericht verzendt. Hier heb je jezelf niet expliciet gebeld, maar het besturingssysteem/VM heeft het voor je gedaan.

Om ze aan te pakken, moet je je code onderzoeken. Als je functies hebt die zichzelf aanroepen, controleer dan of je een beëindigingsvoorwaarde hebt. Als dat het geval is, controleer dan of u bij het aanroepen van de functie ten minste een van de argumenten hebt gewijzigd, anders is er geen zichtbare verandering voor de recursief aangeroepen functie en is de beëindigingsvoorwaarde nutteloos. Houd er ook rekening mee dat uw stapelruimte onvoldoende geheugen kan hebben voordat een geldige beëindigingsvoorwaarde wordt bereikt, dus zorg ervoor dat uw methode invoerwaarden aankan die meer recursieve aanroepen vereisen.

Als je geen duidelijke recursieve functies hebt, controleer dan of je bibliotheekfuncties aanroept die indirect ervoor zorgen dat je functie wordt aangeroepen (zoals het impliciete geval hierboven).

p>


Antwoord 2, autoriteit 29%

Laten we om dit te beschrijven eerst begrijpen hoe lokale variabelen en objecten worden opgeslagen.

Lokale variabele wordt opgeslagen in stack:
voer hier de afbeeldingsbeschrijving in

Als je naar de afbeelding kijkt, zou je moeten kunnen begrijpen hoe de dingen werken.

Wanneer een functieaanroep wordt aangeroepen door een Java-toepassing, wordt een stapelframe toegewezen aan de aanroepstack. Het stapelframe bevat de parameters van de aangeroepen methode, de lokale parameters en het retouradres van de methode. Het retouradres geeft het uitvoeringspunt aan van waaruit de uitvoering van het programma zal doorgaan nadat de aangeroepen methode terugkeert. Als er geen ruimte is voor een nieuw stackframe, wordt de StackOverflowError gegenereerd door de Java Virtual Machine (JVM).

Het meest voorkomende geval dat de stack van een Java-toepassing kan uitputten, is recursie. Bij recursie roept een methode zichzelf aan tijdens de uitvoering ervan. Recursie wordt beschouwd als een krachtige programmeertechniek voor algemeen gebruik, maar moet met de nodige voorzichtigheid worden gebruikt om StackOverflowError te voorkomen.

Een voorbeeld van het gooien van een StackOverflowError wordt hieronder weergegeven:

StackOverflowErrorExample.java:

public class StackOverflowErrorExample {
  public static void recursivePrint(int num) {
    System.out.println("Number: " + num);
    if (num == 0)
      return;
    else
      recursivePrint(++num);
  }
    public static void main(String[] args) {
      StackOverflowErrorExample.recursivePrint(1);
    }
}

In dit voorbeeld definiëren we een recursieve methode, genaamd recursivePrint die een geheel getal afdrukt en zichzelf vervolgens aanroept, met het volgende opeenvolgende gehele getal als argument. De recursie eindigt totdat we 0 als parameter doorgeven. In ons voorbeeld hebben we echter de parameter van 1 en de toenemende volgelingen doorgegeven, waardoor de recursie nooit zal eindigen.

Een voorbeelduitvoering, waarbij de vlag -Xss1M wordt gebruikt die de grootte van de thread-stack specificeert tot 1 MB, wordt hieronder weergegeven:

Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
        at java.io.PrintStream.write(PrintStream.java:480)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.write(PrintStream.java:527)
        at java.io.PrintStream.print(PrintStream.java:669)
        at java.io.PrintStream.println(PrintStream.java:806)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        ...

Afhankelijk van de initiële configuratie van de JVM kunnen de resultaten verschillen, maar uiteindelijk zal de StackOverflowError worden gegenereerd. Dit voorbeeld is een heel goed voorbeeld van hoe recursie problemen kan veroorzaken als het niet met de nodige voorzichtigheid wordt geïmplementeerd.

Hoe om te gaan met de StackOverflowError

  1. De eenvoudigste oplossing is om de stacktrace zorgvuldig te inspecteren en
    detecteer het herhalende patroon van regelnummers. Deze regelnummers
    geeft aan dat de code recursief wordt aangeroepen. Zodra u deze detecteert
    regels, moet u uw code zorgvuldig inspecteren en begrijpen waarom de
    recursie eindigt nooit.

  2. If you have verified that the recursion
    is implemented correctly, you can increase the stack’s size, in
    order to allow a larger number of invocations. Depending on the Java
    Virtual Machine (JVM) installed, the default thread stack size may
    equal to either **512KB, or 1MB**. You can increase the thread stack
    size using the `-Xss` flag. This flag can be specified either via the
    project’s configuration, or via the command line. The format of the
    `-Xss` argument is:
    `-Xss<size>[g|G|m|M|k|K]`
    

Antwoord 3, autoriteit 15%

Als je een functie hebt zoals:

int foo()
{
    // more stuff
    foo();
}

Dan blijft foo() zichzelf aanroepen, wordt dieper en dieper, en wanneer de ruimte die wordt gebruikt om bij te houden in welke functies je zit, vol is, krijg je de stack overflow-fout.


Antwoord 4, autoriteit 5%

Stack overflow betekent precies dat: een stack loopt over. Gewoonlijk is er één stapel in het programma die variabelen van het lokale bereik bevat en adressen waarnaar moet worden teruggekeerd wanneer de uitvoering van een routine eindigt. Die stapel heeft meestal een vast geheugenbereik ergens in het geheugen, daarom is het beperkt in hoeveel het waarden kan bevatten.

Als de stapel leeg is, kun je niet knallen, als je dat wel doet, krijg je een onderstroomfout.

Als de stapel vol is, kun je niet pushen. Als je dat wel doet, krijg je een stack overflow-fout.

Dus stapeloverloop verschijnt wanneer u te veel aan de stapel toewijst. Bijvoorbeeld in de genoemde recursie.

Sommige implementaties optimaliseren sommige vormen van recursie. Staartrecursie in het bijzonder. Tail recursieve routines zijn vorm van routines waarbij de recursieve oproep verschijnt als een laatste ding wat de routine doet. Zo’n routinegesprek wordt eenvoudigweg gereduceerd tot een sprong.

Sommige implementaties gaan zelfs zo ver dat ze hun eigen stacks voor recursie implementeren, daarom laten ze de recursie doorgaan totdat het systeem geen geheugen meer heeft.

Het makkelijkste wat je zou kunnen proberen is om je stack te vergroten als je kunt. Als je dat echter niet kunt, is het op een na beste om te kijken of er iets is dat duidelijk de stack overflow veroorzaakt. Probeer het door iets voor en na de oproep in routine af te drukken. Dit helpt je om de falende routine te achterhalen.


Antwoord 5, autoriteit 2%

Een stackoverloop wordt meestal aangeroepen door functieaanroepen te diep te nesten (vooral gemakkelijk bij het gebruik van recursie, dwz een functie die zichzelf aanroept) of door een grote hoeveelheid geheugen toe te wijzen aan de stapel waar het gebruik van de heap meer geschikt zou zijn.

p>


Antwoord 6, autoriteit 2%

Zoals je zegt, je moet wat code laten zien. 🙂

Een stack-overflow-fout treedt meestal op wanneer uw functie nest te diep aanroept. Zie de Stack Overflow Code Golf-thread voor enkele voorbeelden van hoe dit gebeurt (hoewel in de in het geval van die vraag veroorzaken de antwoorden met opzet een stack overflow).


Antwoord 7

De meest voorkomende oorzaak van stack-overflows is buitensporig diepe of oneindige recursie. Als dit je probleem is, kan deze tutorial over Java-recursie je helpen het probleem te begrijpen.


Antwoord 8

StackOverflowError is voor de stapel als OutOfMemoryError voor de hoop.

Onbegrensde recursieve aanroepen leiden ertoe dat de stapelruimte wordt opgebruikt.

Het volgende voorbeeld levert StackOverflowError op:

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }
    public static void main(String[] args) 
    {
        unboundedRecursiveCall();
    }
}

StackOverflowError kan worden vermeden als recursieve aanroepen worden begrensd om te voorkomen dat het totaal van onvolledige in-memory aanroepen (in bytes) de stapelgrootte (in bytes) overschrijdt.


Antwoord 9

Hier is een voorbeeld van een recursief algoritme voor het omkeren van een enkelvoudig gekoppelde lijst. Op een laptop met de volgende specificaties (4G-geheugen, Intel Core i5 2,3 GHz CPU, 64 bit Windows 7), zal deze functie een StackOverflow-fout tegenkomen voor een gekoppelde lijst met een grootte van bijna 10.000.

Mijn punt is dat we recursie verstandig moeten gebruiken, altijd rekening houdend met de schaal van het systeem.
Vaak kan recursie worden geconverteerd naar een iteratief programma, dat beter schaalt. (Een iteratieve versie van hetzelfde algoritme wordt onder aan de pagina gegeven, het keert een enkelvoudig gelinkte lijst van grootte 1 miljoen in 9 milliseconden om.)

    private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){
    LinkedListNode second = first.next;
    first.next = x;
    if(second != null){
        return doReverseRecursively(first, second);
    }else{
        return first;
    }
}
public static LinkedListNode reverseRecursively(LinkedListNode head){
    return doReverseRecursively(null, head);
}

Iteratieve versie van hetzelfde algoritme:

    public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}   
private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {
    while (first != null) {
        LinkedListNode second = first.next;
        first.next = x;
        x = first;
        if (second == null) {
            break;
        } else {
            first = second;
        }
    }
    return first;
}
public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}

Antwoord 10

Een StackOverflowError is een runtime-fout in Java.

Het wordt gegenereerd wanneer de hoeveelheid call-stackgeheugen die door JVM is toegewezen, wordt overschreden.

Een veelvoorkomend geval van een StackOverflowError die wordt gegenereerd, is wanneer de call-stack wordt overschreden als gevolg van overmatige diepe of oneindige recursie.

Voorbeeld:

public class Factorial {
    public static int factorial(int n){
        if(n == 1){
            return 1;
        }
        else{
            return n * factorial(n-1);
        }
    }
    public static void main(String[] args){
        System.out.println("Main method started");
        int result = Factorial.factorial(-1);
        System.out.println("Factorial ==>"+result);
        System.out.println("Main method ended");
    }
}

Stacktracering:

Main method started
Exception in thread "main" java.lang.StackOverflowError
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)

In het bovenstaande geval kan dit worden vermeden door programmatische wijzigingen aan te brengen.
Maar als de programmalogica correct is en het komt nog steeds voor, dan moet de stapelgrootte worden vergroot.


Antwoord 11

De stapel heeft een ruimtelimiet die afhankelijk is van het besturingssysteem, de normale grootte is 8 MB (in Ubuntu kun je die limiet controleren met $ ulimit -u en het kan op dezelfde manier worden gecontroleerd in andere besturingssystemen ). Elk programma maakt tijdens runtime gebruik van de stapel, maar om volledig te weten wanneer deze wordt gebruikt, moet u een assembleertaal controleren. In x86_64 wordt de stapel bijvoorbeeld gebruikt om:

  1. Bewaar het retouradres bij het maken van een procedureaanroep
  2. Lokale variabelen opslaan
  3. Bewaar speciale registers om ze later te herstellen
  4. Argumenten doorgeven aan een procedureaanroep (meer dan 6)
  5. Overig: willekeurige ongebruikte stapelbasis, kanariewaarden, opvulling, … enz.

Als je x86_64 (normaal geval) niet kent, hoef je alleen te weten wanneer de specifieke programmeertaal met een hoog niveau die je gebruikt, naar die acties compileert. Bijvoorbeeld in C:

  • (1) > een functie-aanroep
  • (2) > lokale variabelen in functie-aanroepen (inclusief hoofd)
  • (3) > lokale variabelen in functie-aanroepen (niet hoofd)
  • (4) > een functie-aanroep
  • (5) > normaal gesproken een functieaanroep, het is over het algemeen niet relevant voor een stapeloverloop.

Dus in C maken alleen lokale variabelen en functie-aanroepen gebruik van de stapel. De 2 (unieke?) manieren om een ​​stack overflow te maken zijn:

  • Te grote lokale variabelen declareren in de main of in een functie die wordt aangeroepen (int array[10000][10000];)
  • Een zeer diepe of oneindige recursie (te veel functieaanroepen tegelijk).

Om een ​​StackOverflowError te vermijden, kunt u:

  • controleer of lokale variabelen te groot zijn (volgorde van 1 MB) > gebruik de heap (malloc/calloc-aanroepen) of globale variabelen.

  • controleer op oneindige recursie > je weet wat je moet doen… corrigeer het!

  • controleer op normale te diepe recursie > de gemakkelijkste benadering is om de implementatie gewoon te veranderen zodat deze iteratief is.

Merk ook op dat globale variabelen, bibliotheken, enz… geen gebruik maken van de stapel.

Alleen als het bovenstaande niet werkt, wijzigt u de stapelgrootte naar het maximum op het specifieke besturingssysteem. Met Ubuntu bijvoorbeeld: $ ulimit -s 32768 (32 MB). (Dit is nooit de oplossing geweest voor mijn stack overflow-fouten, maar ik heb ook niet veel ervaring)

Ik heb speciale en/of niet-standaardgevallen in C weggelaten (zoals het gebruik van alloc() en dergelijke), want als je ze gebruikt, zou je al precies moeten weten wat je doet.

Proost!


Antwoord 12

In een crisis zal de onderstaande situatie een Stack overflow-fout veroorzaken.

public class Example3 {
public static void main(String[] args) {
    main(new String[1]);
}

}


Antwoord 13

Eenvoudig Java-voorbeeld, dat java.lang.StackOverflowError veroorzaakt vanwege een slechte recursieve aanroep

class Human {
    Human(){
        new Animal();       
    }
}
class Animal extends Human {
    Animal(){
        super();
    }
}
public class Test01 {
    public static void main(String[] args) {
        new Animal();
    }
}

Antwoord 14

Hier is een voorbeeld

public static void main(String[] args) {
    System.out.println(add5(1));
}
public static int add5(int a) {
    return add5(a) + 5;
}

Een StackOverflowError is eigenlijk wanneer je iets probeert te doen, dat zichzelf hoogstwaarschijnlijk aanroept en oneindig doorgaat (of totdat het een StackOverflowError geeft).

add5(a) roept zichzelf op, en roept zichzelf weer op, enzovoort.


Antwoord 15

Dit is een typisch geval van java.lang.StackOverflowError… De methode roept zichzelf recursief aan zonder exit in doubleValue(), floatValue(), enz.

Rational.java

    public class Rational extends Number implements Comparable<Rational> {
        private int num;
        private int denom;
        public Rational(int num, int denom) {
            this.num = num;
            this.denom = denom;
        }
        public int compareTo(Rational r) {
            if ((num / denom) - (r.num / r.denom) > 0) {
                return +1;
            } else if ((num / denom) - (r.num / r.denom) < 0) {
                return -1;
            }
            return 0;
        }
        public Rational add(Rational r) {
            return new Rational(num + r.num, denom + r.denom);
        }
        public Rational sub(Rational r) {
            return new Rational(num - r.num, denom - r.denom);
        }
        public Rational mul(Rational r) {
            return new Rational(num * r.num, denom * r.denom);
        }
        public Rational div(Rational r) {
            return new Rational(num * r.denom, denom * r.num);
        }
        public int gcd(Rational r) {
            int i = 1;
            while (i != 0) {
                i = denom % r.denom;
                denom = r.denom;
                r.denom = i;
            }
            return denom;
        }
        public String toString() {
            String a = num + "/" + denom;
            return a;
        }
        public double doubleValue() {
            return (double) doubleValue();
        }
        public float floatValue() {
            return (float) floatValue();
        }
        public int intValue() {
            return (int) intValue();
        }
        public long longValue() {
            return (long) longValue();
        }
    }

Main.java

    public class Main {
        public static void main(String[] args) {
            Rational a = new Rational(2, 4);
            Rational b = new Rational(2, 6);
            System.out.println(a + " + " + b + " = " + a.add(b));
            System.out.println(a + " - " + b + " = " + a.sub(b));
            System.out.println(a + " * " + b + " = " + a.mul(b));
            System.out.println(a + " / " + b + " = " + a.div(b));
            Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
                    new Rational(5, 1), new Rational(4, 1),
                    new Rational(3, 1), new Rational(2, 1),
                    new Rational(1, 1), new Rational(1, 2),
                    new Rational(1, 3), new Rational(1, 4),
                    new Rational(1, 5), new Rational(1, 6),
                    new Rational(1, 7), new Rational(1, 8),
                    new Rational(1, 9), new Rational(0, 1)};
            selectSort(arr);
            for (int i = 0; i < arr.length - 1; ++i) {
                if (arr[i].compareTo(arr[i + 1]) > 0) {
                    System.exit(1);
                }
            }
            Number n = new Rational(3, 2);
            System.out.println(n.doubleValue());
            System.out.println(n.floatValue());
            System.out.println(n.intValue());
            System.out.println(n.longValue());
        }
        public static <T extends Comparable<? super T>> void selectSort(T[] array) {
            T temp;
            int mini;
            for (int i = 0; i < array.length - 1; ++i) {
                mini = i;
                for (int j = i + 1; j < array.length; ++j) {
                    if (array[j].compareTo(array[mini]) < 0) {
                        mini = j;
                    }
                }
                if (i != mini) {
                    temp = array[i];
                    array[i] = array[mini];
                    array[mini] = temp;
                }
            }
        }
    }

Resultaat

    2/4 + 2/6 = 4/10
    Exception in thread "main" java.lang.StackOverflowError
    2/4 - 2/6 = 0/-2
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
    2/4 * 2/6 = 4/24
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
    2/4 / 2/6 = 12/8
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)

Hier is de broncode van StackOverflowError in OpenJDK 7


Antwoord 16

De term “stack overrun (overflow)” wordt vaak gebruikt, maar een verkeerde benaming; aanvallen lopen niet over de stapel heen, maar bufferen op de stapel.

— van collegedia’s van Prof. Dr. Dieter Gollmann

LEAVE A REPLY

Please enter your comment!
Please enter your name here

19 − 12 =

Other episodes