कोशिश अंत में ब्लॉक StackOverflowError को रोकता है


331

निम्नलिखित दो विधियों पर एक नज़र डालें:

public static void foo() {
    try {
        foo();
    } finally {
        foo();
    }
}

public static void bar() {
    bar();
}

रनिंग में bar()स्पष्ट रूप से परिणाम होता है StackOverflowError, लेकिन रनिंग foo()नहीं करता है (प्रोग्राम सिर्फ अनिश्चित काल तक चलने लगता है)। ऐसा क्यों है?


17
औपचारिक रूप से, कार्यक्रम अंततः बंद हो जाएगा क्योंकि finallyक्लॉज के प्रसंस्करण के दौरान फेंकी गई त्रुटियां अगले स्तर तक फैल जाएंगी। लेकिन अपनी सांस को रोककर मत रखो; उठाए गए कदमों की संख्या लगभग 2 से (अधिकतम स्टैक गहराई) होगी और अपवादों को फेंकना बिलकुल भी सस्ता नहीं है।
डोनाल्ड फेलो

3
हालांकि यह "सही" होगा bar()
dan04

6
@ dan04: जावा पूर्ण स्टैक निशानों को सुनिश्चित करने के लिए TCO, IIRC नहीं करता है, और प्रतिबिंब से संबंधित कुछ के लिए (संभवतः स्टैक के निशान के साथ भी करना है)।
नंजलज

4
दिलचस्प बात यह है कि जब मैंने इसे .Net (मोनो का उपयोग करते हुए) पर आज़माया था, तो प्रोग्राम अंततः बिना किसी कॉल के StackOverflow त्रुटि के साथ क्रैश हो गया।
किब्बी

10
यह मेरे द्वारा देखे गए कोड के सबसे खराब टुकड़े के बारे में है :)
poitroae

जवाबों:


332

यह हमेशा के लिए नहीं चलता है। प्रत्येक स्टैक ओवरफ़्लो अंत में कोड को ब्लॉक करने का कारण बनता है। समस्या यह है कि यह वास्तव में, वास्तव में लंबा समय लेगा। समय का क्रम ओ (2 ^ एन) है जहां एन अधिकतम स्टैक गहराई है।

कल्पना कीजिए कि अधिकतम गहराई 5 है

foo() calls
    foo() calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
    finally calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
finally calls
    foo() calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
    finally calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()

अंत में ब्लॉक में प्रत्येक स्तर पर काम करने के लिए दो बार लगते हैं जब तक कि स्टैक की गहराई 10,000 या अधिक हो सकती है। यदि आप प्रति सेकंड 10,000,000 कॉल कर सकते हैं, तो यह ब्रह्मांड की उम्र से 10 ^ 3003 सेकंड या उससे अधिक समय लेगा।


4
अच्छा, भले ही मैं स्टैक को जितना संभव हो उतना छोटा बनाने की कोशिश करता -Xssहूं, मुझे [150 - 210] की गहराई मिलती है, इसलिए 2 ^ n छोर एक [47 - 65] अंकों की संख्या है। उस लंबे समय तक इंतजार नहीं करना, मेरे लिए अनंत के पास पर्याप्त है।
नवजाल

64
@oldrinb सिर्फ तुम्हारे लिए, मैंने गहराई को 5 तक बढ़ाया;)
पीटर लॉरी

4
तो, दिन के अंत में जब fooअंत में समाप्त होता है, तो इसका परिणाम क्या होगा StackOverflowError?
अर्शजी

5
गणित के बाद, हाँ। अंतिम स्टैक ओवरफ्लो अंतिम अंत से जो स्टैक ओवरफ्लो को विफल करने में विफल रहा है ... स्टैक ओवरफ्लो = पी। विरोध नहीं कर सका।
हूजक्रिग

1
तो क्या वास्तव में इसका मतलब यह भी है कि कोड को पकड़ने की कोशिश भी स्टैकओवरफ्लो त्रुटि में समाप्त होनी चाहिए ??
LPD

40

जब आप foo()अंदर के आह्वान से अपवाद प्राप्त करते हैं try, तो आप फिर foo()से कॉल करते हैं finallyऔर फिर से शुरू करते हैं। जब वह एक और अपवाद का कारण बनता है, तो आप foo()दूसरे आंतरिक से कॉल करेंगे finally(), और इसी तरह लगभग विज्ञापन infinitum पर


5
संभवतः, एक StackOverflowError (SOE) तब भेजा जाता है जब नए तरीकों को कॉल करने के लिए स्टैक पर अधिक जगह नहीं होती है। एक SOE के बादfoo() आखिरकार कॉल कैसे किया जा सकता है ?
16

4
@assylias: यदि पर्याप्त जगह नहीं है, तो आप नवीनतम foo()आह्वान से लौटेंगे , और अपने वर्तमान आह्वान foo()के finallyब्लॉक में foo()आह्वान करेंगे।
नंजलज

+1 से ननजाल। आप से foo फोन नहीं होगा कहीं भी एक बार आप अतिप्रवाह स्थिति के कारण foo फोन नहीं कर सकते हैं। इसमें अंत में ब्लॉक शामिल है, यही वजह है कि अंततः (ब्रह्मांड की उम्र) समाप्त हो जाती है।
WhozCraig

38

निम्नलिखित कोड चलाने का प्रयास करें:

    try {
        throw new Exception("TEST!");
    } finally {
        System.out.println("Finally");
    }

आप पाएंगे कि अंत में एक अपवाद को ऊपर के स्तर तक फेंकने से पहले निष्पादित करता है। (आउटपुट:

आखिरकार

थ्रेड में अपवाद "मुख्य" java.lang.Exception: टेस्ट! test.main पर (test.java:6)

यह समझ में आता है, जैसा कि विधि से बाहर निकलने से पहले सही कहा जाता है। हालांकि, इसका मतलब यह है कि एक बार जब आप पहली बार प्राप्त करते हैं StackOverflowError, तो इसे फेंकने की कोशिश करेंगे, लेकिन अंत में पहले निष्पादित करना होगा, इसलिए यह foo()फिर से चलता है , जो एक और स्टैक ओवरफ्लो हो जाता है, और जैसे कि अंत में फिर से चलता है। यह हमेशा के लिए होता रहता है, इसलिए अपवाद वास्तव में कभी भी मुद्रित नहीं होता है।

हालाँकि, आपकी बार विधि में, जैसे ही अपवाद होता है, इसे सीधे ऊपर के स्तर तक सीधे फेंक दिया जाता है, और मुद्रित किया जाएगा


2
Downvote। "हमेशा होता रहता है" गलत है। अन्य उत्तर देखें।
jcsahnwaldt

26

उचित सबूत प्रदान करने के प्रयास में कि यह आखिरकार समाप्त हो जाएगा, मैं निम्नलिखित बल्कि अर्थहीन कोड प्रदान करता हूं। नोट: जावा मेरी भाषा नहीं है, सबसे ज्वलंत कल्पना के किसी भी खिंचाव से। मैं इसे केवल पीटर के उत्तर का समर्थन करने के लिए मानता हूं, जो प्रश्न का सही उत्तर है।

यह तब होता है जब एक आह्वान नहीं हो सकता है क्योंकि यह एक ढेर अतिप्रवाह शुरू होता है की स्थितियों को अनुकरण करने का प्रयास करता है। मुझे ऐसा लगता है सबसे मुश्किल बात यह है कि लोगों में समझ है कि आह्वान नहीं होता है जब यह विफल हो रहे हैं नहीं कर सकते हो।

public class Main
{
    public static void main(String[] args)
    {
        try
        {   // invoke foo() with a simulated call depth
            Main.foo(1,5);
        }
        catch(Exception ex)
        {
            System.out.println(ex.toString());
        }
    }

    public static void foo(int n, int limit) throws Exception
    {
        try
        {   // simulate a depth limited call stack
            System.out.println(n + " - Try");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@try("+n+")");
        }
        finally
        {
            System.out.println(n + " - Finally");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@finally("+n+")");
        }
    }
}

Goo के इस छोटे बिंदुहीन ढेर का आउटपुट निम्नलिखित है, और पकड़ा गया वास्तविक अपवाद आश्चर्य के रूप में आ सकता है; ओह, और 32 कोशिश-कॉल (2 ^ 5), जो पूरी तरह से अपेक्षित है:

1 - Try
2 - Try
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
2 - Finally
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
1 - Finally
2 - Try
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
2 - Finally
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
java.lang.Exception: StackOverflow@finally(5)

23

अपना प्रोग्राम ट्रेस करना सीखें:

public static void foo(int x) {
    System.out.println("foo " + x);
    try {
        foo(x+1);
    } 
    finally {
        System.out.println("Finally " + x);
        foo(x+1);
    }
}

यह वह आउटपुट है जो मैं देख रहा हूं:

[...]
foo 3439
foo 3440
foo 3441
foo 3442
foo 3443
foo 3444
Finally 3443
foo 3444
Finally 3442
foo 3443
foo 3444
Finally 3443
foo 3444
Finally 3441
foo 3442
foo 3443
foo 3444
[...]

जैसा कि आप देख सकते हैं कि StackOverFlow को ऊपर कुछ परतों पर फेंक दिया गया है, इसलिए आप अतिरिक्त पुनरावृत्ति कदम तब तक कर सकते हैं जब तक आप एक और अपवाद नहीं मारते हैं, और इसी तरह। यह एक अनंत "पाश" है।


11
यह वास्तव में अनंत लूप नहीं है, यदि आप पर्याप्त धैर्य रखते हैं तो यह अंततः समाप्त हो जाएगा। मैं हालांकि इसके लिए अपनी सांस नहीं रोकूंगा।
रेयान

4
मैं कहता हूं कि यह अनंत है। हर बार जब यह अधिकतम स्टैक की गहराई तक पहुँचता है तो यह एक अपवाद फेंकता है और स्टैक को खोल देता है। हालांकि अंत में इसे फू कहते हैं, जिससे यह फिर से उस स्टैक स्पेस का फिर से उपयोग कर सकता है, जो अभी बरामद हुआ है। यह अपवादों को फेंकते हुए आगे और पीछे जाएगा और फिर जब तक यह फिर से नहीं हो जाता, तब तक स्टैक को वापस करना। सदैव।
किब्बी

इसके अलावा, आप चाहते हैं कि पहले system.out.println को कोशिश स्टेटमेंट में रखा जाए, अन्यथा यह लूप को उससे आगे खोल देगा। संभवतः इससे रुकावट पैदा हो सकती है।
कबि

1
@ तिब्बी आपके तर्क के साथ समस्या यह है कि जब यह fooदूसरी बार, finallyब्लॉक में कॉल करता है , तो यह अब ए में नहीं है try। इसलिए जब यह स्टैक के नीचे वापस जाएगा और एक बार अधिक स्टैक ओवरफ्लो बनाएगा, तो दूसरी बार यह fooरी-डेंनिंग के बजाय दूसरी कॉल द्वारा उत्पन्न त्रुटि को फिर से हटा देगा।
अमलॉयल

0

कार्यक्रम केवल हमेशा के लिए चलने लगता है; यह वास्तव में समाप्त हो जाता है, लेकिन आपके पास अधिक स्टैक स्पेस होने में अधिक समय लगता है। यह साबित करने के लिए कि यह खत्म हो गया है, मैंने एक प्रोग्राम लिखा है जो पहले उपलब्ध स्टैक स्पेस में से अधिकांश को हटाता है, और फिर कॉल करता है foo, और अंत में जो हुआ उसका एक ट्रेस लिखता है:

foo 1
  foo 2
    foo 3
    Finally 3
  Finally 2
    foo 3
    Finally 3
Finally 1
  foo 2
    foo 3
    Finally 3
  Finally 2
    foo 3
    Finally 3
Exception in thread "main" java.lang.StackOverflowError
    at Main.foo(Main.java:39)
    at Main.foo(Main.java:45)
    at Main.foo(Main.java:45)
    at Main.foo(Main.java:45)
    at Main.consumeAlmostAllStack(Main.java:26)
    at Main.consumeAlmostAllStack(Main.java:21)
    at Main.consumeAlmostAllStack(Main.java:21)
    ...

कोड:

import java.util.Arrays;
import java.util.Collections;
public class Main {
  static int[] orderOfOperations = new int[2048];
  static int operationsCount = 0;
  static StackOverflowError fooKiller;
  static Error wontReachHere = new Error("Won't reach here");
  static RuntimeException done = new RuntimeException();
  public static void main(String[] args) {
    try {
      consumeAlmostAllStack();
    } catch (RuntimeException e) {
      if (e != done) throw wontReachHere;
      printResults();
      throw fooKiller;
    }
    throw wontReachHere;
  }
  public static int consumeAlmostAllStack() {
    try {
      int stackDepthRemaining = consumeAlmostAllStack();
      if (stackDepthRemaining < 9) {
        return stackDepthRemaining + 1;
      } else {
        try {
          foo(1);
          throw wontReachHere;
        } catch (StackOverflowError e) {
          fooKiller = e;
          throw done; //not enough stack space to construct a new exception
        }
      }
    } catch (StackOverflowError e) {
      return 0;
    }
  }
  public static void foo(int depth) {
    //System.out.println("foo " + depth); Not enough stack space to do this...
    orderOfOperations[operationsCount++] = depth;
    try {
      foo(depth + 1);
    } finally {
      //System.out.println("Finally " + depth);
      orderOfOperations[operationsCount++] = -depth;
      foo(depth + 1);
    }
    throw wontReachHere;
  }
  public static String indent(int depth) {
    return String.join("", Collections.nCopies(depth, "  "));
  }
  public static void printResults() {
    Arrays.stream(orderOfOperations, 0, operationsCount).forEach(depth -> {
      if (depth > 0) {
        System.out.println(indent(depth - 1) + "foo " + depth);
      } else {
        System.out.println(indent(-depth - 1) + "Finally " + -depth);
      }
    });
  }
}

आप इसे ऑनलाइन आज़मा सकते हैं ! (कुछ रन fooदूसरों की तुलना में अधिक या कम कॉल कर सकते हैं)

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.