IT이야기

try-catch 내에서 무한 재귀의 혼란스러운 출력

cyworld 2021. 10. 11. 17:41
반응형

try-catch 내에서 무한 재귀의 혼란스러운 출력


다음 코드를 고려하십시오.

public class Action {
private static int i=1;
public static void main(String[] args) {
    try{
        System.out.println(i);
        i++;
        main(args);
    }catch (StackOverflowError e){
        System.out.println(i);
        i++;
        main(args);
    }
 }

}

나는 4338올바른 값을 얻고 있습니다. StackOverflowError출력을 잡은 후 다음과 같이 배선됩니다.

4336
4337
4338 // up to this point out put can understand 
433943394339 // 4339 repeating thrice  
434043404340
4341
434243424342
434343434343
4344
4345
434643464346
434743474347
4348
434943494349
435043504350

여기에서 라이브 데모를 고려 하십시오. 까지 올바르게 작동합니다 i=4330. 실제로 어떻게 이런 일이 발생합니까?

참고로:

나는 여기서 무슨 일이 일어나고 있는지 알기 위해 다음 코드를 수행했습니다.

public class Action {

    private static int i = 1;
    private static BufferedWriter bw;

    static {
        try {
            bw = new BufferedWriter(new FileWriter("D:\\sample.txt"));
        } catch (IOException e) {
           e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        bw.append(String.valueOf(i)+" ");
        try {
            i++;
            main(args);
        } catch (StackOverflowError e) {
            bw.append(String.valueOf(i)+" ");
            i++;
            main(args);
        }
    }

}

이제 이전 문제가 없습니다. 이제 i최대 16824744수정하고 더 실행합니다. 나는 이것이 i=2,147,483,647문제없이 (int의 최대 값) 값까지 실행될 수 있기를 바랍니다 .

에 문제가 println()있습니다. 아래에도 비슷한 답변이 있습니다. 하지만 왜?

실제 이유는 무엇입니까?


에는 개행 문자가 없습니다 433943394339. 내부에 문제가 발생함을 나타냅니다 System.out.println().

필수 포인트는 여기 즉 System.out.println()그가 있으므로 작업에 일부 스택 공간을 필요로 StackOverflowError에서 발생합니다 System.out.println().

표시된 점이 있는 코드는 다음과 같습니다.

public static void main(String[] args) {
    try{
        System.out.println(i); // (1)
        i++;
        main(args); // (2)
    }catch (StackOverflowError e){
        System.out.println(i); // (3)
        i++;
        main(args); // (4)
    }
}

i = 인 경우 재귀 수준 N에서 어떤 일이 발생하는지 상상해 봅시다 4338.

  • 수준 N의 명령문 (1)은 을 인쇄합니다 4338. 산출4338\n
  • i 로 증가합니다 4339
  • 제어 흐름은 (2)에서 레벨 N + 1에 들어갑니다.
  • 수준 N + 1의 명령문 (1)은 인쇄를 시도 4339하지만 개행을 인쇄하기 전에 System.out.println()던집니다 StackOverflowError. 산출4339
  • StackOverflowError레벨 N + 1에서 잡히면 문 (3)은 인쇄를 시도 4339하고 같은 이유로 다시 실패합니다. 산출4339
  • 수준 N에서 예외가 발생했습니다. 이 시점에서 사용 가능한 스택 공간이 더 있으므로 명령문 (3)이 인쇄를 시도 4339하고 성공합니다(개행 문자가 올바르게 인쇄됨). 산출4339\n
  • i 가 증가하고 제어 흐름은 (4)에서 다시 N + 1 레벨로 들어갑니다.

이 시점 이후 상황은 로 반복됩니다 4340.

일부 숫자가 개행 없이 시퀀스 사이에 정확히 인쇄되는 이유가 확실하지 않습니다. 아마도 내부 작업 System.out.println()및 사용하는 버퍼와 관련이 있을 것 입니다.


내가 의심하는 것은 다음과 같습니다.

  1. 인쇄 나
  2. 줄 바꿈 인쇄
  3. 나는 증가
  4. 메인 입력
  5. 인쇄 나
  6. 줄 바꿈 인쇄
  7. 나는 증가
  8. 메인 입력
  9. 인쇄 나
  10. StackOverflow가 발생했습니다(인쇄 개행 대신).
  11. 메인으로 돌아가기, 지금 캐치 중
  12. 인쇄 나
  13. StackOverflow가 다시 발생했습니다(인쇄 개행 대신).
  14. 다른 catch 본문에서 메인으로 돌아갑니다.
  15. 인쇄 나
  16. 줄 바꿈 인쇄
  17. main을 입력하고 1로 돌아갑니다.

내 테스트에 따르면 :

try 블록에 의해 Exception이 발생하면 icatch 블록에 들어올 때와 동일한 값을 가집니다(예외로 인해 증가하지 않기 때문에).

그런 다음 catch 블록 내부에서 동일한 예외가 throw되고 catch 블록에 의해 다시 catch됩니다!

나는 다음 코드를 시도했다

try {
            System.out.println("Try " + i);
            i++;
            main(args);
        } catch (StackOverflowError e) {
            System.out.println("\nBefore");
            System.out.println("Catch " + i);
            i++;
            System.out.println("After");
            main(args);

        }

출력:

Try 28343
Try 28344
Before
Before
Before
Before
Catch 28344
After
Try 28345
Try 28346
Try 28347
Try 28348
Before
Before
Before
Before
Catch 28348
After
Try 28349

try 블록에서 예외가 발생하면 catch 블록에서 catch되지만 System.out.println("Catch " + i);다시 발생 하면 예외가 4번 발생합니다(내 이클립스에서) 인쇄하지 않고System.out.println("Catch " + i);

위의 출력에서와 같이 인쇄하기 전에 네 번 인쇄되는 "Before" 텍스트를 인쇄하여 테스트했습니다. System.out.println("Catch " + i);


실행 println(또는 호출된 메서드 중 하나)으로 인해 스택 오버플로가 발생 i하면 바깥쪽 main구현 의 catch 절에서 동일한 값을 인쇄합니다 .

정확한 동작은 여전히 ​​사용 가능한 스택 공간에 따라 달라지므로 예측할 수 없습니다.


다른 답변에서 이미 설명했듯이 스택에 추가 공간이 필요한 System.out.println과 관련이 있으므로 스스로 StackOverflowError를 발생시킵니다.

여기에서 이 코드를 시도하여 i++가 더 이상 발생할 수 없도록 모든 곳에서 예외가 발생하는 시점을 보여주는 몇 가지 다른 동작을 확인하십시오.

public class Action {
  private static int i = 1;

  private static StringBuffer buffer = new StringBuffer();

  public static void main(String[] args) {
    try {
      print();
      main(args);
    }
    catch (StackOverflowError e) {
      print();
      main(args);
    }
  }

  private static void print() {
    buffer.append(i).append("\n");
    i++;
    if (i % 1000 == 0) {
      System.out.println("more: " + buffer);
      buffer = new StringBuffer();
    }
  }
}

배워야 할 중요한 교훈은 다음과 같습니다. 정상적으로 처리할 수 없는 JVM에 심각한 문제가 있음을 나타내는 오류를 절대 포착하지 마십시오.


결론 StackOverflowError은 예외가 아니라 오류라는 것입니다. 예외가 아니라 오류를 처리하고 있습니다. 따라서 프로그램은 catch 블록에 들어갈 때 이미 충돌했습니다. 프로그램의 이상한 동작과 관련하여 아래는 공식 문서 의 Java 버퍼를 기반으로 한 설명입니다 .

예를 들어 autoflush PrintWriter 객체는 println 또는 format을 호출할 때마다 버퍼를 플러시합니다.

System.out.println()내부적으로 호출 PrintStream버퍼링되는합니다. 버퍼에서 데이터를 잃지 않고 채워진 후 또는 명시적으로 플러시를 호출할 때 모든 데이터가 출력(귀하의 경우 터미널)에 기록됩니다.

이 시나리오로 돌아가면 스택이 얼마나 채워지고 캐치 인에서 실행할 수 있는 인쇄 문과 main()버퍼에 기록된 문자 수의 내부 역학에 따라 달라집니다 . 여기서 첫 번째 시도가 실행된 후, 즉 스택 오버플로가 처음 발생한 경우 첫 번째 System.out.println()은 새 줄을 인쇄하지 못하므로 버퍼를 나머지 문자로 플러시합니다.


axtavt 답변은 매우 완전하지만 다음을 추가하고 싶습니다.

스택이 변수 메모리를 저장하는 데 사용된다는 것을 알 수 있듯이 제한에 도달하면 새 변수를 만들 수 없다는 것을 기반으로 System.out.println에 일부 스택 리소스가 필요한 것이 사실입니다.

787     public void More ...println(Object x) {
788         String s = String.valueOf(x);
789         synchronized (this) {
790             print(s);
791             newLine();
792         }
793     }

그런 다음 인쇄를 호출한 후 오류로 인해 newLine을 호출할 수도 없고 인쇄에서 바로 다시 중단됩니다. 이를 기반으로 다음과 같이 코드를 변경하여 해당 사항을 확인할 수 있습니다.

public class Action {
    static int i = 1;

    public static void main(String[] args) {
        try {
            System.out.print(i + "\n");
            i++;
            main(args);
        } catch (StackOverflowError e) {
            System.out.print(i + " SO " + "\n");
            i++;
            main(args);
        }
    }
}

이제 스택에 새 줄을 처리하도록 요청하지 않고 상수 "\n"을 사용하고 예외 인쇄 줄에 일부 디버깅을 추가할 수 있으며 출력은 같은 줄에 여러 값을 갖지 않습니다.

10553
10553 SO
10553 SO
10554
10554 SO
10554 SO
10555
10556
10557
10558

그리고 새 데이터를 할당하고 다음 i 값으로 전달할 일부 리소스를 얻을 때까지 계속 손상됩니다.

ReferenceURL : https://stackoverflow.com/questions/18311305/confusing-output-from-infinite-recursion-within-try-catch

반응형