Question

Qu'est-ce qu'un StackOverflowError, quelles en sont les causes et comment dois-je les gérer?

Était-ce utile?

La solution

Les paramètres et les variables locales sont alloués sur la pile (avec les types de référence, l'objet vit sur le segment de mémoire et une variable de la pile référence l'objet sur le segment de mémoire). . La pile se trouve généralement à l'extrémité supérieure de votre espace d'adressage. Lorsqu'elle est utilisée, elle se dirige vers le bas de cet espace (c.-à-d. Vers zéro).

Votre processus comporte également un segment de mémoire , qui se situe à l'extrémité inférieure de votre processus. Lorsque vous allouez de la mémoire, ce segment de mémoire peut atteindre la limite supérieure de votre espace d'adressage. Comme vous pouvez le constater, il est possible que le segment de mémoire & "; Se heurte &"; à la pile (un peu comme des plaques tectoniques !!!).

La cause fréquente d'un débordement de pile est un mauvais appel récursif . En règle générale, cela est dû au fait que vos fonctions récursives ne présentent pas la condition de terminaison correcte; elles finissent donc par s'appeler elles-mêmes pour toujours. Ou bien, lorsque la condition de terminaison est correcte, cela peut être dû à un trop grand nombre d'appels récursifs avant de le remplir.

Cependant, avec la programmation graphique, il est possible de générer une récursion indirecte . Par exemple, votre application peut gérer des messages de peinture et, lors de leur traitement, appeler une fonction qui force le système à envoyer un autre message de peinture. Ici, vous ne vous êtes pas explicitement appelé, mais l’OS / VM l’a fait pour vous.

Pour les traiter, vous devez examiner votre code. Si vous avez des fonctions qui s'appellent elles-mêmes, vérifiez que vous avez une condition de fin. Si tel est le cas, vérifiez que lorsque vous appelez la fonction, vous avez au moins modifié l'un des arguments, sinon il n'y aura aucun changement visible pour la fonction appelée récursivement et la condition de terminaison est inutile. N'oubliez pas non plus que votre espace de pile peut manquer de mémoire avant d'atteindre une condition de terminaison valide. Assurez-vous donc que votre méthode peut gérer les valeurs d'entrée nécessitant des appels plus récursifs.

Si vous n'avez aucune fonction récursive évidente, vérifiez si vous appelez des fonctions de bibliothèque qui indirectement provoqueront l'appel de votre fonction (comme dans le cas implicite ci-dessus).

Autres conseils

Pour décrire cela, commençons par comprendre comment sont stockés les variables et les objets locaux .

Les variables locales sont stockées dans la pile : entrer la description de l'image ici

Si vous regardez l'image, vous devriez pouvoir comprendre comment les choses fonctionnent.

Lorsqu'un appel de fonction est appelé par une application Java, un cadre de pile est alloué sur la pile d'appels. Le cadre de pile contient les paramètres de la méthode invoquée, ses paramètres locaux et l'adresse de retour de la méthode. L'adresse de retour indique le point d'exécution à partir duquel l'exécution du programme doit continuer après le retour de la méthode invoquée. S'il n'y a pas d'espace pour un nouveau cadre de pile, le StackOverflowError est envoyé par la machine virtuelle Java (JVM).

Le cas le plus courant pouvant éventuellement épuiser la pile d'une application Java & # 8217; est la récursivité. En récursion, une méthode s’appelle elle-même lors de son exécution. La récursivité est considérée comme une technique de programmation puissante polyvalente, mais doit être utilisée avec prudence pour éviter recursivePrint.

Un exemple de lancement d'un 0 est présenté ci-dessous:

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

Dans cet exemple, nous définissons une méthode récursive, appelée -Xss1M, qui affiche un entier, puis s’appelle elle-même, l’entier suivant étant l’argument. La récursivité se termine jusqu'à ce que nous passions en paramètre -Xss. Cependant, dans notre exemple, nous avons passé le paramètre de 1 et son nombre croissant de suiveurs. Par conséquent, la récursivité ne se terminera jamais.

Un exemple d'exécution, utilisant l'indicateur -Xss<size>[g|G|m|M|k|K] qui spécifie que la taille de la pile de threads doit être égale à 1 Mo, est présenté ci-dessous:

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)
        ...

En fonction de la configuration initiale de la machine virtuelle Java & # 8217; les résultats peuvent différer, mais le <=> doit être renvoyé. Cet exemple est un très bon exemple de la façon dont la récursivité peut causer des problèmes, si elle n'est pas implémentée avec prudence.

Comment gérer l'erreur StackOverflowError

  1. La solution la plus simple consiste à inspecter avec soin la trace de la pile et détecter le motif répété des numéros de ligne. Ces numéros de ligne indique le code appelé de manière récursive. Une fois que vous détectez ces lignes, vous devez soigneusement inspecter votre code et comprendre pourquoi le la récursivité ne se termine jamais.

  2. Si vous avez vérifié que la récursivité     est correctement implémenté, vous pouvez augmenter la taille de la pile & # 8217; en     afin de permettre un plus grand nombre d'invocations. En fonction du Java     Machine virtuelle (JVM) installée, la taille de pile de thread par défaut peut     égal à 512 Ko ou à 1 Mo . Vous pouvez augmenter la pile de threads     taille en utilisant le drapeau <=>. Cet indicateur peut être spécifié soit via le     la configuration du projet & # 8217; ou via la ligne de commande. Le format de la     <=> argument est:     <=>

Si vous avez une fonction comme:

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

Ensuite, foo () continuera à s’appeler lui-même, devenant de plus en plus profond, et lorsque l’espace utilisé pour garder trace des fonctions dans lesquelles vous vous trouvez est rempli, vous obtenez l’erreur de débordement de pile.

Dépassement de pile signifie exactement cela: une pile déborde. Il y a généralement une pile dans le programme qui contient les variables de portée locale et les adresses où retourner lorsque l'exécution d'une routine se termine. Cette pile a tendance à être une plage de mémoire fixe quelque part dans la mémoire. Par conséquent, son contenu peut être limité.

Si la pile est vide, vous ne pouvez pas la supprimer. Si vous le faites, vous obtiendrez une erreur de dépassement de pile.

Si la pile est pleine, vous ne pouvez pas pousser, sinon vous obtiendrez une erreur de débordement de pile.

Donc, le dépassement de pile apparaît là où vous allouez trop dans la pile. Par exemple, dans la récursivité mentionnée.

Certaines implémentations optimisent certaines formes de récursivité. Queue récursive en particulier. Les routines récursives de queue sont des formes de routines dans lesquelles l'appel récursif apparaît comme la dernière chose que fait la routine. Un tel appel de routine se réduit simplement à un saut.

Certaines implémentations vont jusqu'à implémenter leurs propres piles pour la récursivité. Elles permettent donc à la récursivité de continuer jusqu'à ce que le système manque de mémoire.

La chose la plus simple que vous puissiez essayer serait d’augmenter la taille de votre pile si vous le pouvez. Si vous ne pouvez pas faire cela, la deuxième meilleure chose à faire serait de vérifier si quelque chose cause clairement le débordement de pile. Essayez-le en imprimant quelque chose avant et après l'appel dans la routine. Cela vous aide à identifier la routine défaillante.

Un débordement de pile est généralement appelé par des appels de fonction imbriqués trop profonds (particulièrement facile lorsque vous utilisez la récursivité, c’est-à-dire une fonction qui s’appelle elle-même) ou en allouant une grande quantité de mémoire sur la pile où l’utilisation du segment de mémoire serait plus appropriée.

Comme vous le dites, vous devez afficher du code. : -)

Une erreur de débordement de pile se produit généralement lorsque vos appels de fonction imbriquent trop profondément. Voir le fil Stack Overflow Code Golf pour des exemples montrant comment cela se produit (bien que cas de cette question, les réponses provoquent intentionnellement un débordement de pile).

La cause la plus fréquente de débordements de pile est la récursion excessivement profonde ou infinie . Si tel est votre problème, ce didacticiel sur la récursion Java peut vous aider à comprendre le problème.

StackOverflowError correspond à la pile de la même manière que OutOfMemoryError au tas.

Les appels récursifs non liés entraînent l’utilisation de l’espace de pile.

L'exemple suivant produit <=>:

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }

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

<=> est évitable si les appels récursifs sont liés pour empêcher le total cumulé des appels en mémoire incomplets (en octets) de dépasser la taille de la pile (en octets).

Voici un exemple d’algorithme récursif permettant d’inverser une liste chaînée. Sur un ordinateur portable répondant aux spécifications suivantes (mémoire 4G, processeur Intel Core i5 à 2,3 GHz, Windows 7 64 bits), cette fonction rencontrera une erreur StackOverflow pour une liste chaînée de taille proche de 10 000.

Ce que je veux dire, c'est que nous devrions utiliser la récursion de manière judicieuse, en tenant toujours compte de la taille du système. La récursivité peut souvent être convertie en programme itératif, qui évolue mieux. (Une version itérative du même algorithme est donnée au bas de la page. Elle inverse une liste simple de 1 million en 9 millisecondes.)

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

Version itérative du même algorithme:

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

Un StackOverflowError est une erreur d'exécution en java.

Il est émis lorsque la quantité de mémoire de la pile d'appels allouée par la machine virtuelle Java est dépassée.

Un cas courant de <=> lancement est le cas où la pile d'appels dépasse en raison d'une récursion excessive ou infinie excessive.

Exemple:

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

Trace de pile:

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)

Dans le cas ci-dessus, cela peut être évité en faisant des changements de programme. Mais si la logique du programme est correcte et que cela se produit toujours, la taille de votre pile doit être augmentée.

Voici un exemple

public static void main(String[] args) {
    System.out.println(add5(1));
}

public static int add5(int a) {
    return add5(a) + 5;
}

Une erreur StackOverflowError se définit lorsque vous essayez de faire quelque chose qui s’appelle le plus probablement lui-même et continue à l’infini (ou jusqu’à ce qu’il donne une erreur StackOverflowError).

add5(a) s'appellera, puis s'appellera à nouveau, et ainsi de suite.

Il s'agit d'un cas typique de java.lang.StackOverflowError ... La méthode s'appelle de manière récursive sans sortie dans doubleValue(), floatValue(), etc.

.

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

Résultat

    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)

ici est le code source de StackOverflowError dans OpenJDK 7

Le terme & "; pile en dépassement (débordement) &"; est souvent utilisé, mais un abus de langage; les attaques ne débordent pas de la pile mais des tampons sur la pile.

- extrait du diaporama de Prof. Dr. Dieter Gollmann

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top