web-dev-qa-db-fra.com

Qu'est-ce qui provoque réellement une erreur de débordement de pile?

J'ai regardé partout et je ne trouve pas de réponse solide. Selon la documentation, Java génère une erreur Java.lang.StackOverflowError dans les circonstances suivantes:

Émis lorsqu'un débordement de pile se produit car une application est trop récursive.

Mais cela soulève deux questions:

  • N'y a-t-il pas d'autres moyens de provoquer un débordement de pile, pas seulement par récursion?
  • Est-ce que StackOverflowError survient avant que la JVM ne déborde réellement de la pile ou après?

Pour élaborer sur la deuxième question:

Lorsque Java lève StackOverflowError, pouvez-vous sans risque supposer que la pile n'a pas écrit dans le tas? Si vous réduisez la taille de la pile ou du tas dans une fonction try/catch sur une fonction générant un débordement de pile, pouvez-vous continuer à travailler? Est-ce documenté quelque part?

Réponses que je ne cherche pas:

  • Un StackOverflow se produit en raison d'une mauvaise récursivité.
  • Un StackOverflow se produit lorsque le tas rencontre la pile.
234
retrohacker

Il semble que vous pensiez qu'un erreur de stackoverflow est comme une exception de dépassement de tampon dans les programmes natifs, lorsqu'il existe un risque d'écriture dans la mémoire qui n'avait pas été allouée pour le tampon, et donc de corrompre certains autres emplacements de mémoire. Ce n'est pas le cas du tout.

JVM a une mémoire donnée allouée pour chaque pile de chaque thread et si une tentative d'appel d'une méthode arrive à remplir cette mémoire, JVM renvoie une erreur. Comme vous le feriez si vous essayiez d'écrire à l'index N d'un tableau de longueur N. Aucune corruption de mémoire ne peut se produire. La pile ne peut pas écrire dans le tas.

Une erreur StackOverflowError est à la pile ce qu'une erreur OutOfMemoryError est au tas: elle signale simplement qu'il n'y a plus de mémoire disponible.

Description à partir d'erreurs de machine virtuelle (§6.3)

StackOverflowError : l'implémentation de la machine virtuelle Java n'a plus d'espace de pile pour un thread, généralement parce que le thread effectue un nombre illimité. d'appels récursifs à la suite d'une erreur dans le programme en cours d'exécution.

195
JB Nizet

N'y a-t-il pas d'autres moyens de provoquer un débordement de pile, pas seulement par récursion?

Sûr. Continuez à appeler des méthodes sans jamais y retourner. Vous aurez toutefois besoin de beaucoup de méthodes, sauf si vous autorisez la récursivité. En réalité, cela ne fait aucune différence: un cadre de pile est un cadre de pile, qu'il s'agisse d'une méthode récursive ou non, c'est la même chose.

La réponse à votre deuxième question est la suivante: le stackoverflow est détecté lorsque la machine virtuelle Java tente d'allouer le cadre de la pile pour le prochain appel et découvre que ce n'est pas possible. Donc, rien ne sera écrasé.

52
Ingo

N'y a-t-il pas d'autres moyens de provoquer un débordement de pile, pas seulement par récursion?

Défi accepté :) StackOverflowError sans récursion (défi échoué, voir les commentaires):

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

Compilez avec javac Test.Java standard et exécutez-vous avec Java -Xss104k Test 2> out. Après cela, more out vous dira:

Exception in thread "main" Java.lang.StackOverflowError

Deuxième essai.

Maintenant, l'idée est encore plus simple. Les primitives dans Java peuvent être stockées sur la pile. Alors déclarons beaucoup de doublons, comme double a1,a2,a3.... Ce script peut écrire, compiler et exécuter le code pour nous:

#!/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

Et ... j'ai quelque chose d'inattendu:

#
# 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

C'est 100% répétitif. Ceci est lié à votre deuxième question:

Est-ce que StackOverflowError survient avant que la JVM ne déborde réellement de la pile ou après?

Ainsi, dans le cas d’OpenJDK 20.0-b12, nous pouvons voir que la machine virtuelle Java a tout d’abord explosé. Mais cela ressemble à un bug, peut-être que quelqu'un peut confirmer cela dans les commentaires s'il vous plaît, parce que je ne suis pas sûr. Dois-je signaler cela? Peut-être que cela est déjà corrigé dans une version plus récente ... Selon lien de spécification JVM (donné par JB Nizet dans un commentaire) JVM devrait lancer une StackOverflowError, pas mourir:

Si le calcul dans un thread nécessite une pile Java de machine virtuelle plus grande que celle autorisée, la machine virtuelle Java renvoie une erreur StackOverflowError.


Troisième essai.

public class Test {
    Test test = new Test();

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

Nous voulons créer un nouvel objet Test. Donc, son constructeur (implicite) sera appelé. Mais, juste avant cela, tous les membres de Test sont initialisés. Donc, Test test = new Test() est exécuté en premier ...

Nous voulons créer un nouvel objet Test ...

Mise à jour: Pas de chance, c'est de la récursion, j'ai posé une question à ce sujet ici .

27

Il n'y a pas "StackOverFlowException". Ce que vous voulez dire, c'est "StackOverFlowError".

Oui, vous pouvez continuer à travailler si vous l'attrapez parce que la pile est effacée lorsque vous le faites, mais ce serait une option mauvaise et laide.

Quand exactement l'erreur est-elle levée? - Lorsque vous appelez une méthode et que la machine virtuelle vérifie si la mémoire est suffisante pour le faire. Bien sûr, l'erreur est levée si ce n'est pas possible.

  • Non, c’est la seule façon d’obtenir cette erreur: obtenir votre pile pleine. Mais pas seulement par la récursivité, mais également par l’appel de méthodes appelant d’autres méthodes à l’infini. C'est une erreur très spécifique, alors non.
  • Il est lancé avant que la pile ne soit pleine, au moment précis où vous le vérifiez. Où placeriez-vous les données s'il n'y a pas d'espace disponible? Remplacer les autres? Naah.
3
GabrielBB

La cause la plus courante de StackOverFlowError est une récursion excessivement profonde ou infinie.

Par exemple:

public int yourMethod(){
       yourMethod();//infinite recursion
}

En Java:

Il y a two zones en mémoire le tas et la pile. Le stack memory sert à stocker les variables locales et l'appel de fonction, tandis que heap memory sert à stocker les objets en Java.

S'il ne reste plus de mémoire dans la pile pour stocker l'appel de fonction ou la variable locale, la machine virtuelle Java lancera Java.lang.StackOverFlowError

tandis que s'il n'y a plus d'espace de tas pour créer un objet, JVM lancera Java.lang.OutOfMemoryError

3
Lazy Coder

Il y a deux endroits principaux où les choses peuvent être stockées en Java. Le premier est le tas, utilisé pour les objets alloués dynamiquement. new.

De plus, chaque thread en cours d’exécution reçoit sa propre pile et reçoit une quantité de mémoire allouée à cette pile.

Lorsque vous appelez une méthode, les données sont ensuite insérées dans la pile pour enregistrer l'appel de la méthode, les paramètres transmis et les variables locales allouées. Une méthode avec cinq variables locales et trois paramètres utilisera plus d'espace de pile qu'une méthode void doStuff() sans aucune variable locale.

Les principaux avantages de la pile sont qu’il n’ya pas de fragmentation de la mémoire, que tout pour un appel de méthode est alloué en haut de la pile et que le retour depuis les méthodes est simple. Pour revenir d'une méthode, il vous suffit de dérouler la pile à la méthode précédente, définissez la valeur nécessaire pour la valeur de retour et vous avez terminé.

Parce que la pile est une taille fixe par thread (notez que la spécification Java ne nécessite pas de taille fixe, mais que la plupart des implémentations JVM au moment de l'écriture utilisent une taille fixe) et que l'espace sur la pile est réduit. nécessaire chaque fois que vous effectuez un appel de méthode, espérons-le, il devrait maintenant être clair pourquoi elle peut s'épuiser et ce qui peut le causer. Il n'y a pas un nombre fixe d'appels de méthode, il n'y a rien de spécifique à propos de la récursivité, vous obtenez l'exception avec laquelle vous essayez d'appeler une méthode et la mémoire est insuffisante.

Bien entendu, la taille des piles est suffisamment élevée pour qu'il soit hautement improbable que cela se produise dans le code standard. Dans le code récursif, il peut être assez facile de rentrer à des profondeurs énormes, et à ce stade, vous commencez à rencontrer cette erreur.

3
Tim B

StackOverflowError survient à cause d'une application trop récursive (ce n'est pas une réponse que vous attendez).

Maintenant, d'autres choses doivent arriver à StackOverflowError continuer à appeler des méthodes à partir de méthodes jusqu'à ce que vous obteniez StackOverflowError, mais personne ne peut programmer pour obtenir StackOverflowError et même si ces programmeurs le font, ils ne suivent pas. normes de codage pour complixity cyclomatic que chaque programmeur doit comprendre lors de la programmation. Une telle raison pour 'StackOverflowError' demandera beaucoup de temps pour la rectifier.

Mais, sans le savoir, coder une ligne ou deux lignes, ce qui cause StackOverflowError est compréhensible, et JVM le lance et nous pouvons le corriger immédiatement. Ici est ma réponse avec une photo pour une autre question.

2
AmitG

En c #, vous pouvez obtenir un dépassement de pile de manière différente en définissant de manière erronée les propriétés d'un objet. Par exemple :

private double hours;

public double Hours
        {
            get { return Hours; }
            set { Hours = value; }
        }

Comme vous pouvez le constater, les heures restituées avec un H majuscule continueront indéfiniment, ce qui en tant que tel renverra des heures, etc.

Un débordement de pile se produira souvent également en raison d'un manque de mémoire ou de l'utilisation de langages gérés, car votre gestionnaire de langues (CLR, JRE) détectera que votre code est resté bloqué dans une boucle infinie.

Un StackOverflow se produit lorsqu'un appel de fonction est effectué et que la pile est pleine.

Tout comme une exception ArrayOutOfBoundException. Il ne peut rien corrompre, en fait, il est très possible de l'attraper et de s'en remettre.

Cela se produit généralement à la suite d'une récursion incontrôlée, mais peut également être provoqué par le simple fait d'appeler une pile très profonde de fonctions.

0
njzk2