110 votes

Pourquoi cette méthode imprime 4 ?

Je me demandais ce qui se passe lorsque vous essayez d'attraper un StackOverflowError et est venu avec la méthode suivante:

class RandomNumberGenerator {

    static int cnt = 0;

    public static void main(String[] args) {
        try {
            main(args);
        } catch (StackOverflowError ignore) {
            System.out.println(cnt++);
        }
    }
}

Maintenant, ma question:

Pourquoi cette méthode d'impression '4'?

J'ai pensé que c'était peut-être parce qu' System.out.println() des besoins de 3 segments sur la pile des appels, mais je ne sais pas où le numéro 3 vient de. Lorsque vous regardez le code source (et bytecode) System.out.println(), il le ferait normalement conduire à beaucoup plus d'appels de méthode que de 3 (donc 3 segments sur la pile d'appel ne serait pas suffisant). Si c'est parce que des optimisations du Hotspot VM s'applique (méthode inline), je me demande si le résultat serait différent sur une autre machine virtuelle.

Edit:

La sortie semble être très JVM spécifique, j'obtiens le résultat à l'aide de 4
Java(TM) SE Runtime Environment (build 1.6.0_41-b02)
Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01, en mode mixte)


Explication des raisons pour lesquelles je pense que cette question est différente de la Compréhension de java pile:

Ma question est pas de savoir pourquoi il y a un cnt > 0 (évidemment parce System.out.println() exige de la taille de la pile et le lance à un autre StackOverflowError avant que quelque chose est imprimé), mais pourquoi il a de la valeur spécifique de 4, respectivement 0,3,8,55 ou quelque chose d'autre sur d'autres systèmes.

41voto

John Tseng Points 3782

Je pense que les autres ont fait un bon travail en expliquant pourquoi la cnt > 0, mais il n'y a pas assez de détails concernant les raisons de la cnt = 4, et pourquoi cnt varie considérablement entre les différents paramètres. Je vais tenter de combler ce vide ici.

Laissez

  • Soit X le total de la taille de la pile
  • M l'espace de pile utilisé lorsque nous entrons principal de la première fois
  • R l'espace de pile chaque fois que nous entrons dans la principale
  • P la pile de l'espace nécessaire pour exécuter System.out.println

Quand nous recevons dans la principale, l'espace est X-M. Chaque appel récursif prend R plus de mémoire. Donc, pour 1 appel récursif (1 de plus que l'original), l'utilisation de la mémoire est M + R. Supposons que StackOverflowError est jeté après C réussie appels récursifs, c'est-à M + C * R <= X et M + C * (R + 1) > X. Au moment de la première StackOverflowError, il y a X - M - C * R de la mémoire à gauche.

Pour être en mesure d'exécuter System.out.prinln, nous avons besoin de P la quantité d'espace restant sur la pile. Si il se trouve que X - M - C * R >= P, 0 sera imprimé. Si P nécessite plus d'espace, alors nous retirer des images à partir de la pile, gagnant de la R de la mémoire au détriment de la cnt++.

Lors de l' println est enfin en mesure d'exécuter, X - M - C - cnt) * R >= P. si P est grand pour un système particulier, puis cnt sera grand.

Regardons cela avec quelques exemples.

Exemple 1: Supposons Que

  • X = 100
  • M = 1
  • R = 2
  • P = 1

Alors C = floor((X-M)/R) = 49, et cnt = plafond((P - (X - M - C*R)/R) = 0.

Exemple 2: Supposons que

  • X = 100
  • M = 1
  • R = 5
  • P = 12

Alors C = 19, et cnt = 2.

Exemple 3: Supposons que

  • X = 101
  • M = 1
  • R = 5
  • P = 12

Alors C = 20, et cnt = 3.

Exemple 4: Supposons que

  • X = 101
  • M = 2
  • R = 5
  • P = 12

Alors C = 19, et cnt = 2.

Ainsi, nous voyons que le système (M, R et P) et de la taille de la pile (X) affecte la cnt.

Comme une note de côté, il n'a pas d'importance combien d'espace catch nécessite pour commencer. Tant qu'il n'y a pas assez d'espace pour catch, alors cnt ne sera pas augmenter, donc il n'y a pas d'effets externe.

MODIFIER

Je reprends ce que j'ai dit à propos de catch. Elle joue un rôle. Supposons qu'il nécessite T quantité d'espace pour commencer. cnt commence à cran lorsque l'espace restant est supérieur à T, et println s'exécute lorsque l'espace restant est supérieur à T + P. Cela ajoute une étape supplémentaire pour les calculs et autres brouille jusqu'à la déjà boueux analyse.

MODIFIER

J'ai enfin trouvé le temps de lancer quelques expériences pour sauvegarder ma théorie. Malheureusement, la théorie ne semble pas correspondre avec les expériences. Ce qui se passe en réalité est très différente.

L'expérience de l'installation: Ubuntu 12.04 server avec java par défaut et default-jdk. Xss départ à 70 000 à 1 octet des augmentations de 460 000.

Les résultats sont disponibles sur: https://www.google.com/fusiontables/DataSource?docid=1xkJhd4s8biLghe6gZbcfUs3vT5MpS_OnscjWDbM J'ai créé une autre version où répété chaque point de données est supprimé. En d'autres termes, seuls les points qui sont différentes de la précédente sont indiquées. Cela rend plus facile de voir les anomalies. https://www.google.com/fusiontables/DataSource?docid=1XG_SRzrrNasepwZoNHqEAKuZlHiAm9vbEdwfsUA

20voto

Sajal Dutta Points 4896

C'est à la victime de mauvais appel récursif. Comme vous vous demandez pourquoi la valeur de la cnt varie, c'est parce que la taille de la pile dépend de la plate-forme. Java SE 6 sur Windows a une taille de pile par défaut de 320k dans le 32 bits VM et 1024k dans la version 64 bits de VM. Vous pouvez en lire plus ici.

Vous pouvez exécuter à l'aide de différentes tailles de tapis et vous pourrez voir les différentes valeurs de la cnt avant les débordements de pile-

java -Xss1024k RandomNumberGenerator

Vous ne verrez pas la valeur de la cnt d'être imprimé plusieurs fois, même si la valeur est supérieure à 1 parfois parce que votre instruction print est aussi de lancer d'erreur que vous pouvez déboguer pour être assurer par Eclipse ou d'autres IDEs.

Vous pouvez modifier le code à la suite de debug par l'exécution de l'instruction si vous préférez-

static int cnt = 0;

public static void main(String[] args) {                  

    try {     

        main(args);   

    } catch (Throwable ignore) {

        cnt++;

        try { 

            System.out.println(cnt);

        } catch (Throwable t) {   

        }        
    }        
}

Mise à JOUR:

Comme ce que beaucoup plus d'attention, nous allons avoir un autre exemple pour rendre les choses plus claires-

static int cnt = 0;

public static void overflow(){

    try {     

      overflow();     

    } catch (Throwable t) {

      cnt++;                      

    }

}

public static void main(String[] args) {

    overflow();
    System.out.println(cnt);

}

Nous avons créé une autre méthode appelée dépassement de faire un mauvais récursivité et enlevé la println déclaration du bloc catch afin de ne pas lancer une autre série d'erreurs lors de la tentative d'impression. Cela fonctionne comme prévu. Vous pouvez essayer de mettre le Système..println(cnt); instruction après la cnt++ ci-dessus et de les compiler. Puis exécuter plusieurs fois. En fonction de votre plate-forme, vous pouvez obtenir différentes valeurs de la cnt.

C'est pourquoi, généralement, nous ne capture des erreurs parce que de mystère dans le code n'est pas une fantaisie.

13voto

Jatin Points 8069

Le comportement dépend de la taille de la pile (qui peut être réglée manuellement à l'aide d' Xss. La taille de la pile est spécifique à une architecture. De JDK 7 du code source:

// Taille de pile par défaut sur Windows est déterminé par le fichier exécutable (java.exe
// a une valeur par défaut de 320K/1 MO [32bit/64bit]). Selon la version de Windows, la modification de
// ThreadStackSize non nulle peut avoir un impact important sur l'utilisation de la mémoire.
// Voir les commentaires en os_windows.cpp.

Ainsi, lorsque l' StackOverflowError est levée, l'erreur est pris dans le bloc catch. Ici, println() est une autre pile d'appel qui rejette l'exception de nouveau. Cette répété.

Combien de fois il repeates? - Eh bien, cela dépend de la date de la JVM pense qu'il n'est plus stackoverflow. Et qui dépend de la taille de la pile de chaque appel de fonction (difficile à trouver) et de l' Xss. Comme mentionné ci-dessus par défaut total de la taille et de la taille de chaque appel de fonction (dépend de la taille de page de mémoire, etc) de la plate-forme. D'où des comportements différents.

L'appel de la java appel avec -Xss 4M me donne de l' 41. D'où la correlataion.

6voto

Kazaag Points 950

Je pense que le nombre affiché est le nombre de fois que le System.out.println appel jette l' Stackoverflow d'exception.

C'est probablement dépendent de la mise en œuvre de l' println et le nombre d'empilement c'est fait.

Comme une illustration:

L' main() appel déclencher l' Stackoverflow d'exception lors de l'appel de je. L'i-1 l'appel de la principale intercepter l'exception et appelez - println qui déclenchent une seconde Stackoverflow. cnt obtenir incrémenter de 1. I-2 l'appel de la prise principale devenu l'exception et appelez - println. En println une méthode est appelée à déclencher une 3ème exception. cnt obtenir incrément de 2. cela continue jusqu'à ce qu' println peut faire tous, il est nécessaire d'appel et enfin afficher la valeur de cnt.

C'est alors dépendante de la mise en œuvre effective de l' println.

Pour le JDK7 soit-il détecter le vélo appel et lève l'exception plus tôt, soit à conserver certains pile de ressources et de lancer l'exception avant d'atteindre la limite de donner un peu de place pour d'assainissement de la logique, soit l' println mise en œuvre n'est pas de faire des appels dans le ++ opération est réalisée après l' println appel est donc en passer par l'exception.

6voto

Strilanc Points 7161
  1. main se répète sur lui-même jusqu'à ce qu'il déborde de la pile à la profondeur de récursion R.
  2. Le bloc catch à la profondeur de récursion R-1 est exécuté.
  3. Le bloc catch à la profondeur de récursion R-1 évalue cnt++.
  4. Le bloc catch en profondeur, R-1 des appels println, plaçant cnts'ancienne valeur sur la pile. println en interne appeler d'autres méthodes, et utilise des variables locales et des choses. Tous ces procédés nécessitent l'espace de pile.
  5. Parce que la pile était déjà le pâturage de la limite, et la visite/de l'exécution d' println nécessite espace de pile, un nouveau débordement de pile est déclenché à la profondeur de l' R-1 au lieu de profondeur R.
  6. Les étapes 2 à 5 se produire à nouveau, mais à la profondeur de récursion R-2.
  7. Les étapes 2 à 5 se produire à nouveau, mais à la profondeur de récursion R-3.
  8. Les étapes 2 à 5 se produire à nouveau, mais à la profondeur de récursion R-4.
  9. Les étapes 2 à 4 se produire à nouveau, mais à la profondeur de récursion R-5.
  10. Il se trouve qu'il y a assez d'espace de pile maintenant pour println pour terminer (à noter que c'est un détail d'implémentation, il peut varier).
  11. cnt a été post-incrémenté à des profondeurs R-1, R-2, R-3, R-4, et enfin à l' R-5. La cinquième post-incrémentation retourné quatre, qui est ce qui a été imprimé.
  12. Avec main terminée avec succès à la profondeur R-5, l'ensemble de la pile se déroule sans plusieurs blocs catch cours d'exécution et le programme se termine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X