Comment gérer StackOverflowError
en Java?
Je ne suis pas sûr de ce que vous voulez dire par "poignée".
Vous pouvez certainement attraper cette erreur:
public class Example {
public static void endless() {
endless();
}
public static void main(String args[]) {
try {
endless();
} catch(StackOverflowError t) {
// more general: catch(Error t)
// anything: catch(Throwable t)
System.out.println("Caught "+t);
t.printStackTrace();
}
System.out.println("After the error...");
}
}
mais c’est probablement une mauvaise idée, à moins de savoir exactement ce que vous faites.
Vous avez probablement une récursion infinie en cours.
C'est à dire. une méthode qui s'appelle encore et encore
public void sillyMethod()
{
sillyMethod();
}
Une solution consiste à réparer votre code afin que la récursivité se termine au lieu de continuer indéfiniment.
Jetez un coup d'œil à Le message de/de Raymond ChenLors du débogage d'un débordement de pile, vous voulez vous concentrer sur la partie récurrente récursive . Un extrait:
Si vous explorez votre base de données de suivi des défauts pour savoir s’il s’agit d’un problème connu ou non, il est peu probable que la recherche des principales fonctions de la pile ne présente aucun intérêt. En effet, les débordements de pile ont tendance à se produire à un moment aléatoire de la récursivité. chaque débordement de pile est superficiellement différent de tous les autres, même s'il s'agit du même débordement de pile.
Supposons que vous chantiez la chanson Frère Jacques, sauf que vous chantez chaque couplet de quelques tons plus haut que le précédent. Finalement, vous atteindrez le sommet de votre plage de chant, et précisément où cela se produit dépend du point où votre limite vocale s'aligne sur la mélodie. Dans la mélodie, les trois premières notes sont chacune un nouveau "record" (c'est-à-dire que les notes sont plus élevées que toute autre note chantée jusqu'à présent), et de nouveaux records atteignent les trois notes de la troisième mesure, et un enregistrement final. haut dans la deuxième note de la cinquième mesure.
Si la mélodie représente l'utilisation de la pile d'un programme, un débordement de pile peut éventuellement se produire à l'un de ces cinq emplacements lors de l'exécution du programme. En d'autres termes, la même récursion sous-jacente (représentée musicalement par une interprétation toujours plus haute de la mélodie) peut se manifester de cinq manières différentes. La "récursion" dans cette analogie était plutôt rapide, seulement huit mesures avant que la boucle ne se répète. Dans la vraie vie, la boucle peut être assez longue, menant à des dizaines de points potentiels où le débordement de pile peut se manifester.
Si vous êtes confronté à un dépassement de capacité de la pile, vous souhaitez alors ignorer le haut de la pile, car vous vous concentrez uniquement sur la note spécifique qui a dépassé votre plage vocale. Vous voulez vraiment trouver la mélodie entière, car c'est ce qui est commun à tous les débordements de pile ayant la même cause fondamentale.
Vous voudrez peut-être voir si l'option "-Xss" est prise en charge par votre machine virtuelle Java. Si tel est le cas, essayez de définir une valeur de 512k (la valeur par défaut est 256k sous Windows 32 bits et Unix) et vérifiez si cela ne fait rien (sauf que vous restez assis plus longtemps jusqu'à l'exception StackOverflowException). Notez qu'il s'agit d'un paramètre par thread. Par conséquent, si vous avez beaucoup de threads en cours d'exécution, vous pouvez également modifier vos paramètres de segment de mémoire.
La réponse correcte est celle déjà donnée. Vous avez probablement soit un bug dans votre code conduisant à une récursion infinie qui est généralement assez facile à diagnostiquer et à corriger, soit b) un code pouvant conduire à des récursions très profondes, par exemple en passant de manière récursive dans un arbre binaire non équilibré. Dans ce dernier cas, vous devez modifier votre code pour ne pas allouer les informations sur la pile (c’est-à-dire pour ne pas renifler) mais pour les allouer dans le tas.
Par exemple, pour une traversée d'arbre non équilibrée, vous pouvez stocker les noeuds qui devront être revisités dans une structure de données Stack. Pour une traversée dans l’ordre, vous devez enrouler les branches de gauche en poussant chaque nœud comme vous l’avez visité jusqu’à ce que vous atteigniez une feuille que vous souhaitez traiter, puis éjectez un nœud du haut de la pile, traitez-le puis redémarrez votre right child (en définissant simplement votre variable de boucle sur le noeud de droite.) Cela utilisera une quantité constante de pile en déplaçant tout ce qui se trouvait sur la pile dans le tas de la structure de données Stack. Heap est généralement beaucoup plus copieux que stack.
En tant que quelque chose qui est généralement une très mauvaise idée, mais est nécessaire dans les cas où l'utilisation de la mémoire est extrêmement contrainte, vous pouvez utiliser l'inversion de pointeur. Dans cette technique, vous encodez la pile dans la structure que vous parcourez et, en réutilisant les liens que vous parcourez, vous pouvez le faire avec pas ou beaucoup moins de mémoire supplémentaire. En utilisant l'exemple ci-dessus, au lieu de pousser des nœuds lorsque nous bouclons, nous devons simplement nous rappeler notre parent immédiat et, à chaque itération, nous définissons le lien que nous avons traversé sur le parent actuel, puis sur le nœud que nous quittons. Lorsque nous arrivons à une feuille, nous la traitons, puis allons chez notre parent et nous avons une énigme. Nous ne savons pas s'il faut corriger la branche de gauche, traiter ce noeud et continuer avec la branche de droite, ou corriger la branche de droite et accéder à notre parent. Nous devons donc allouer un peu d’information supplémentaire lors de notre itération. En règle générale, pour les réalisations de bas niveau de cette technique, ce bit sera stocké dans le pointeur lui-même, ce qui n'entraînera aucune mémoire supplémentaire et une mémoire globale constante. Ce n'est pas une option en Java, mais il est peut-être possible d'épargner ce bit dans des champs utilisés à d'autres fins. Dans le pire des cas, il reste au moins 32 ou 64 fois moins de mémoire disponible. Bien sûr, cet algorithme est extrêmement facile à se tromper avec des résultats complètement déroutants et soulèverait des ravages avec la concurrence. Donc, le cauchemar de la maintenance ne vaut presque jamais, sauf lorsque l’allocation de mémoire est intenable. L'exemple typique étant un ramasse-miettes où des algorithmes comme celui-ci sont courants.
Ce que je voulais vraiment dire, c’est quand vous voudrez peut-être manipuler StackOverflowError. Notamment pour assurer l'élimination des appels de queue sur la machine virtuelle Java. Une approche consiste à utiliser un style de trampoline dans lequel, au lieu d'effectuer un appel final, vous retournez un objet de procédure nullary ou, si vous renvoyez une valeur, vous le retournez. [Remarque: cela nécessite un moyen de dire qu'une fonction retourne A ou B. En Java, le moyen le plus simple de le faire est probablement de retourner un type et de jeter l'autre comme une exception.] Ensuite, chaque fois que vous appelez une méthode, devez faire une boucle while appelant les procédures nullary (qui renverront elles-mêmes une procédure nullary ou une valeur) jusqu'à ce que vous obteniez une valeur. Une boucle sans fin deviendra une boucle while forçant constamment les objets de procédure à renvoyer des objets de procédure. Les avantages du style trampoline sont qu’il n’utilise qu’une quantité de pile constante supérieure à celle que vous utiliseriez avec une implémentation qui éliminait correctement tous les appels en queue, elle utilisait la pile Java normale pour les appels non en queue, la traduction simple et code par un facteur constant (fastidieux). L'inconvénient est que vous allouez un objet à chaque appel de méthode (ce qui deviendra immédiatement un déchet) et que la consommation de ces objets implique deux appels indirects par appel final.L'idéal serait de ne jamais allouer ces procédures nulles ou quoi que ce soit d'autre, ce qui est exactement ce que l'élimination des appels en queue accomplirait. En travaillant avec ce que Java fournit, nous pourrions exécuter le code normalement et ne créer ces procédures nulles que lorsque nous aurions épuisé la pile. Nous allouons toujours ces images inutiles, mais nous le faisons sur la pile plutôt que sur le tas et nous les désallocations en bloc. Nos appels sont également des appels Java directs et normaux. Le moyen le plus simple de décrire cette transformation consiste à réécrire d’abord toutes les méthodes d’instruction multi-appel en méthodes comportant deux instructions d’appel, c.-à-d. Fgh () {f (); g(); h (); } devient fgh () {f (); gh (); } et gh () {g (); h (); }. Pour des raisons de simplicité, je suppose que toutes les méthodes se terminent par un appel final, ce qui peut être organisé en regroupant simplement le reste d'une méthode dans une méthode distincte, bien qu'en pratique, vous souhaitiez les gérer directement. Après ces transformations, nous avons trois cas, soit une méthode a zéro appel, dans ce cas, il n’ya rien à faire, soit elle a un appel (fin), auquel cas nous l’enveloppons dans un bloc try-catch dans le même scénario. l'appel de queue dans le cas de deux appels. Enfin, il peut avoir deux appels, un appel non final et un appel final, auquel cas nous appliquons la transformation suivante illustrée par exemple (en utilisant la notation lambda de C # qui pourrait facilement être remplacée par une classe interne anonyme avec une certaine croissance):.
// top-level handler
Action tlh(Action act) {
return () => {
while(true) {
try { act(); break; } catch(Bounce e) { tlh(() => e.run())(); }
}
}
}
gh() {
try { g(); } catch(Bounce e) {
throw new Bounce(tlh(() => {
e.run();
try { h(); } catch(StackOverflowError e) {
throw new Bounce(tlh(() => h());
}
});
}
try { h(); } catch(StackOverflowError e) {
throw new Bounce(tlh(() => h()));
}
}
Le plus gros problème de cette approche est que vous voudrez probablement appeler des méthodes Java normales et que vous disposerez peut-être de manière arbitraire de peu d’espace de pile, de sorte qu’elles disposeront de suffisamment d’espace pour démarrer, mais pas pour terminer, et vous ne pourrez pas les reprendre dans le milieu. Il y a au moins deux solutions à cela. La première consiste à envoyer tout ce travail à un thread séparé qui aura sa propre pile. Ceci est assez efficace et assez facile et ne crée aucune concurrence (à moins que vous ne le souhaitiez). Une autre option consiste simplement à vider la pile avant d’appeler une méthode Java normale en lançant simplement une exception StackOverflowError immédiatement devant elle. S'il manque toujours d'espace de pile lorsque vous reprenez, vous avez été foutu pour commencer.
Une chose similaire peut être faite pour faire des continuations juste à temps aussi. Malheureusement, cette transformation n’est pas vraiment supportable à la main en Java, elle est probablement à la limite des langages tels que C # ou Scala. Ainsi, les transformations comme celle-ci ont tendance à être effectuées par des langages qui ciblent la machine virtuelle et non par des personnes.
A similar thing can be done to make continuations just-in-time too. Unfortunately, this transformation isn't really bearable to do by hand in Java, and is probably borderline for languages like C# or Scala. So, transformations like this tend to be done by languages that target the JVM and not by people.
La plupart des chances d'obtenir StackOverflowError
se font en utilisant des récursions [long/infinite] dans des fonctions récursives.
Vous pouvez éviter la récursion de fonction en modifiant la conception de votre application pour utiliser des objets de données empilables. Il existe des modèles de codage pour convertir les codes récursifs en blocs de code itératifs. Regardez ci-dessous les réponses:
Ainsi, vous évitez d’empiler la mémoire par Java pour vos appels de fonction récessifs, en utilisant vos propres piles de données.
Je suppose que vous ne pouvez pas - ou au moins cela dépend du JVM que vous utilisez. Le débordement de pile signifie que vous n’avez plus de place pour stocker les variables locales et renvoyer les adresses. Si votre jvm effectue une forme de compilation, vous avez également le stackoverflow dans le jvm et cela signifie que vous ne pouvez pas le gérer ou l'attraper. Le JVM doit se terminer.
Il pourrait y avoir un moyen de créer une JVM qui permette un tel comportement, mais ce serait lent.
Je n'ai pas testé le comportement avec jvm, mais en .net, vous ne pouvez tout simplement pas gérer le stackoverflow. Même essayer attraper ne va pas aider. Puisque Java et .net reposent sur le même concept (machines virtuelles avec jit), je suppose que Java se comporterait de la même manière. La présence d’une exception stackoverflow dans .NET suggère qu’il peut exister une machine virtuelle qui permet au programme de l’attraper, mais pas la normale.
Java.lang.Error javadoc:
Une erreur est une sous-classe de Throwable qui indique des problèmes graves qu’une application raisonnable ne devrait pas tenter d’attraper . La plupart de ces erreurs sont des conditions anormales. L’erreur ThreadDeath, bien qu’elle soit une condition "normale", est également une sous-classe de Error car la plupart des applications ne doivent pas tenter de l’attraper . l'exécution de la méthode mais non interceptée, car ces erreurs sont des conditions anormales qui ne devraient jamais se produire.
Alors, non. Essayez de trouver ce qui ne va pas dans la logique de votre code. Cette exception se produit très souvent à cause d'une récursion infinie.
Simple,
Examinez la trace de pile générée par StackOverflowError afin de savoir où elle se trouve dans votre code et utilisez-la pour déterminer comment réécrire votre code afin qu'il ne s'appelle pas de manière récursive (cause probable de votre erreur) et qu'il gagne. t arriver à nouveau.
StackOverflowErrors ne doit pas être géré via une clause try ... catch, mais il pointe vers un défaut fondamental de la logique de votre code qui doit être corrigé par vous.
dans certaines occasions, vous ne pouvez pas attraper stackoverflowerror. chaque fois que vous essayez, vous en rencontrerez un nouveau. car est Java vm. c'est bien de trouver des blocs de code récursifs, comme dit Andrew Bullock .
La trace de la pile doit indiquer la nature du problème. Il devrait y avoir des boucles évidentes lorsque vous lisez la trace de la pile.
Si ce n'est pas un bogue, vous devez ajouter un compteur ou un autre mécanisme pour arrêter la récursion avant que la récursion ne soit si profonde qu'elle provoque un débordement de pile.
Un exemple de cela pourrait être si vous gérez du XML imbriqué dans un modèle DOM avec des appels récursifs et que le XML est imbriqué si profondément qu'il provoque un débordement de pile avec vos appels imbriqués (improbable, mais possible). Cela devrait être une imbrication assez profonde pour provoquer un débordement de pile.
Comme mentionné par de nombreuses personnes dans ce fil, la cause commune est un appel de méthode récursif qui ne se termine pas. Dans la mesure du possible, évitez le débordement de pile et si vous en testez, vous devriez considérer cela dans la plupart des cas comme un bug sérieux. Dans certains cas, vous pouvez configurer la taille de la pile de threads dans Java afin qu'elle soit plus grande pour gérer certaines situations (grands ensembles de données gérés dans un stockage de pile local, appels récursifs longs), mais cela augmentera l'encombrement mémoire total, ce qui peut entraîner des problèmes de nombre. des threads disponibles dans la VM. Généralement, si vous obtenez cette exception, le fil et toutes les données locales de ce fil doivent être considérés comme du pain grillé et non utilisés (c.-à-d. Suspects et éventuellement corrompus).