Qu’est-ce qui cause réellement une erreur de dépassement de stack?

J’ai regardé partout et je ne trouve pas de réponse solide. Selon la documentation, Java jette une erreur java.lang.StackOverflowError dans les circonstances suivantes:

Lancé lorsqu’un débordement de stack se produit car une application récursive trop profondément.

Mais cela soulève deux questions:

  • N’y a-t-il pas d’autres moyens pour qu’un débordement de stack se produise, pas seulement par récursivité?
  • Est-ce que StackOverflowError se produit avant que la JVM ne déborde réellement la stack ou après?

Pour développer la deuxième question:

Lorsque Java jette le StackOverflowError, pouvez-vous supposer que la stack n’a pas été écrite dans le tas? Si vous réduisez la taille de la stack ou du tas dans un try / catch sur une fonction qui génère un débordement de stack, pouvez-vous continuer à travailler? Est-ce documenté quelque part?

Réponses que je ne recherche pas:

  • Un StackOverflow se produit à cause d’une récursivité incorrecte.
  • Un StackOverflow se produit lorsque le tas rencontre la stack.

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

JVM a une mémoire donnée allouée pour chaque stack de chaque thread et si une tentative d’appeler une méthode arrive à remplir cette mémoire, JVM génère une erreur. Tout comme il le ferait si vous essayiez d’écrire à l’index N d’un tableau de longueur N. Aucune corruption de mémoire ne peut se produire. La stack ne peut pas écrire dans le tas.

Un StackOverflowError est à la stack ce qu’un OutOfMemoryError est au tas: il signale simplement qu’il n’y a plus de mémoire disponible.

Description des erreurs de machine virtuelle (§6.3)

StackOverflowError : l’implémentation de la machine virtuelle Java n’a plus d’espace de stack pour un thread, généralement parce que le thread effectue un nombre illimité d’invocations récursives suite à une défaillance du programme en cours d’exécution.

N’y a-t-il pas d’autres moyens pour qu’un débordement de stack se produise, pas seulement par récursivité?

Sûr. Continuez simplement à appeler les méthodes, sans jamais revenir. Vous aurez besoin de beaucoup de méthodes, sauf si vous autorisez la récursivité. En fait, cela ne fait pas de différence: un frame de stack est un frame de stack, qu’il s’agisse d’une méthode récursive ou non.

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 stack pour le prochain appel et constate que ce n’est pas possible. Donc, rien ne sera écrasé.

N’y a-t-il pas d’autres moyens pour qu’un débordement de stack se produise, pas seulement par récursivité?

Challenge accepté 🙂 StackOverflowError sans récursivité (challenge échoué, voir commentaires):

 public class Test { final static int CALLS = 710; public static void main(Ssortingng[] 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(); } } 

Comstackz avec javac Test.java standard et exécutez java -Xss104k Test 2> out . Après cela, more out choses vous diront:

 Exception in thread "main" java.lang.StackOverflowError 

Deuxième essai.

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

 #!/bin/sh VARIABLES=4000 NAME=Test FILE=$NAME.java SOURCE="public class $NAME{public static void main(Ssortingng[] 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 # Dissortingbution: 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 se produit avant que la JVM ne déborde réellement la stack ou après?

Donc, dans le cas d'OpenJDK 20.0-b12, nous pouvons voir que la JVM a explosé en premier. Mais cela semble être un bug, peut-être que quelqu'un peut le confirmer dans les commentaires, car je ne suis pas sûr. Dois-je signaler cela? Peut-être que c'est déjà corrigé dans une version plus récente ... Selon le lien de spécification JVM (donné par JB Nizet dans un commentaire), la JVM devrait lancer une StackOverflowError , et non mourir:

Si le calcul dans un thread nécessite une stack Java Virtual Machine supérieure à celle autorisée, la machine virtuelle Java génère une erreur StackOverflowError.


Troisième essai

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

Nous souhaitons créer un nouvel object Test . Ainsi, 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 object Test ...

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

La cause la plus commune de StackOverFlowError est une récursion trop profonde ou infinie.

Par exemple:

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

En Java:

Il y a two zones en mémoire dans le tas et la stack. La stack memory est utilisée pour stocker les variables locales et l’appel de fonction, tandis que la heap memory est utilisée pour stocker des objects en Java.

S’il n’y a plus de mémoire dans la stack pour stocker l’appel de fonction ou la variable locale, JVM lancera java.lang.StackOverFlowError

alors que s’il n’y a plus d’espace de tas pour créer un object, JVM lancera java.lang.OutOfMemoryError

Il n’y a pas “StackOverFlowException”. Ce que vous voulez dire par “StackOverFlowError”.

Oui, vous pouvez continuer à travailler si vous le détectez parce que la stack est effacée lorsque vous faites cela, mais ce serait une mauvaise option.

Quand exactement l’erreur est-elle jetée? – Lorsque vous appelez une méthode et que la JVM 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 stack complète. Mais pas seulement à travers la récursivité, mais aussi en appelant des méthodes qui appellent à l’infini d’autres méthodes. C’est une erreur très spécifique donc non.
  • Il est lancé avant que la stack ne soit pleine, exactement quand vous le vérifiez. Où placeriez-vous les données s’il n’y a pas d’espace disponible? Surmonter les autres? Naah.

Il y a deux endroits principaux dans lesquels les choses peuvent être stockées en Java. Le premier est le tas, utilisé pour les objects dynamicment alloués. new

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

Lorsque vous appelez une méthode, les données sont insérées dans la stack pour enregistrer l’appel de méthode, les parameters transmis et toutes les variables locales allouées. Une méthode avec cinq variables locales et trois parameters utilisera plus d’espace de stack qu’une méthode void doStuff() sans variables locales.

Les principaux avantages de la stack sont qu’il n’y a pas de fragmentation de la mémoire, tout ce qui est associé à une méthode est atsortingbué en haut de la stack et que le retour des méthodes est facile. Pour revenir d’une méthode, il vous suffit de dérouler la stack vers la méthode précédente, de définir toute valeur nécessaire pour la valeur de retour et vous avez terminé.

Étant donné que la stack a 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 utilisent une taille fixe) et que la stack nécessite de l’espace Si tout va bien, appelez-le, il devrait maintenant être clair pourquoi il peut s’épuiser et ce qui peut provoquer son épuisement. Il n’y a pas un nombre fixe d’appels de méthodes, il n’y a rien de spécifique à la récursivité, vous obtenez l’exception que vous essayez d’appeler une méthode et il n’y a pas assez de mémoire.

Bien sûr, la taille des stacks est suffisamment élevée pour qu’il soit très improbable qu’elle se produise en code normal. Dans le code récursif, il peut être assez facile de faire appel à de grandes profondeurs, et à ce stade, vous commencez à vous heurter à cette erreur.

StackOverflowError se produit car une application est récursive trop profondément (ceci n’est pas une réponse attendue).

Maintenant, d’autres choses doivent arriver à StackOverflowError : continuez à appeler des méthodes depuis les méthodes jusqu’à ce que vous obteniez StackOverflowError , mais personne ne peut programmer StackOverflowError et même si ces programmeurs le font, elles ne suivent pas les standards de la programmation. Une telle raison pour “StackOverflowError” nécessitera beaucoup de temps pour le corriger.

Mais, sans le savoir, StackOverflowError est StackOverflowError coder une ligne ou deux lignes à l’origine de StackOverflowError et JVM lance cela et nous pouvons le corriger instantanément. Voici ma réponse avec photo pour une autre question.

Un StackOverflow se produit lorsqu’un appel de fonction est effectué et que la stack 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 il peut aussi être causé par un simple appel de fonctions très poussé.

Dans c #, vous pouvez réaliser un débordement de stack de manière différente, en définissant à tort des propriétés d’object. Par exemple :

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

Comme vous pouvez le constater, les Heures reviendront toujours avec un H majuscule, ce qui renverra des Heures, etc.

Un débordement de stack se produit souvent à cause de la mémoire insuffisante ou lors de l’utilisation de langages gérés car votre gestionnaire de langue (CLR, JRE) détectera que votre code est resté bloqué dans une boucle infinie.

Mais cela soulève deux questions:

  1. N’y a-t-il pas d’autres moyens pour qu’un débordement de stack se produise, pas seulement par récursivité?
  2. Est-ce que StackOverflowError se produit avant que la JVM ne déborde réellement la stack ou après?
  1. Cela peut également se produire lorsque nous allouons une taille supérieure à la limite de la stack (par exemple, int x[10000000]; ).

  2. La réponse à la seconde est

Chaque thread a sa propre stack qui contient un cadre pour chaque méthode s’exécutant sur ce thread. La méthode en cours d’exécution est donc en haut de la stack. Une nouvelle image est créée et ajoutée (poussée) en haut de la stack pour chaque invocation de méthode. Le cadre est supprimé (sauté) lorsque la méthode retourne normalement ou si une exception non interceptée est générée lors de l’appel de la méthode. La stack n’est pas directement manipulée, sauf pour les objects frame frame et push, et par conséquent les objects frame peuvent être alloués dans le tas et la mémoire n’a pas besoin d’être contiguë.

Donc, en considérant la stack dans un fil, nous pouvons conclure.

Une stack peut être une taille dynamic ou fixe. Si un thread nécessite une stack plus grande qu’autorisée, une StackOverflowError est lancée. Si un thread requirejs une nouvelle image et qu’il n’y a pas assez de mémoire pour l’allouer, une OutOfMemoryError est OutOfMemoryError .

vous pouvez obtenir la description de la JVM ici