[java] Java 스택 크기를 늘리는 방법은 무엇입니까?

JVM에서 런타임 호출 스택 크기를 늘리는 방법을 알기 위해이 질문을했습니다. 이에 대한 답을 얻었으며 Java가 대규모 런타임 스택이 필요한 상황을 처리하는 방법과 관련된 유용한 답변과 의견도 많이 있습니다. 답변 요약으로 내 질문을 확장했습니다.

원래는 JVM 스택 크기를 늘려서 StackOverflowError.

public class TT {
  public static long fact(int n) {
    return n < 2 ? 1 : n * fact(n - 1);
  }
  public static void main(String[] args) {
    System.out.println(fact(1 << 15));
  }
}

해당 구성 설정은 java -Xss...충분한 값 이있는 명령 줄 플래그입니다. TT위 프로그램 의 경우 OpenJDK의 JVM에서 다음과 같이 작동합니다.

$ javac TT.java
$ java -Xss4m TT

답변 중 하나는 -X...플래그가 구현에 따라 다르다는 것을 지적했습니다 . 나는 사용하고 있었다

java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)

하나의 스레드에 대해서만 큰 스택을 지정할 수도 있습니다 (방법 중 하나에서 참조). java -Xss...필요하지 않은 스레드에 대한 메모리 낭비를 방지하기 위해 권장 됩니다.

위의 프로그램이 정확히 얼마나 큰 스택을 필요로하는지 궁금해서 실행했습니다 n.

  • -Xss4m은 충분할 수 있습니다 fact(1 << 15)
  • -Xss5m은 충분할 수 있습니다 fact(1 << 17)
  • -Xss7m은 충분할 수 있습니다 fact(1 << 18)
  • -Xss9m은 충분할 수 있습니다 fact(1 << 19)
  • -Xss18m은 충분할 수 있습니다 fact(1 << 20)
  • -Xss35m는 충분할 수 있습니다 fact(1 << 21)
  • -Xss68m은 충분할 수 있습니다 fact(1 << 22)
  • -Xss129m는 충분할 수 있습니다 fact(1 << 23)
  • -Xss258m은 충분할 수 있습니다 fact(1 << 24)
  • -Xss515m은 충분할 수 있습니다 fact(1 << 25)

위의 숫자에서 Java는 위의 함수에 대해 스택 프레임 당 약 16 바이트를 사용하는 것으로 보이며 이는 합리적입니다.

스택 요구 사항이 결정적이지 않기 때문에 위의 열거 형 충분 대신 충분할 수 있습니다 . 동일한 소스 파일로 여러 번 실행하면 동일한 소스 파일이 성공하고 . 예를 들어 1 << 20, 10 점 만점에 7 점으로 충분했고, 항상 충분하지는 않았지만 충분했습니다 (100 점 만점에 100 점 모두). 가비지 수집, JIT 시작 또는 다른 것이 이러한 비 결정적 동작을 유발합니까?-Xss...StackOverflowError-Xss18m-Xss19m-Xss20m

a StackOverflowError(및 가능한 다른 예외)에 인쇄 된 스택 추적 은 런타임 스택의 최신 1024 요소 만 표시합니다. 아래 답변은 도달 한 정확한 깊이 (1024보다 훨씬 클 수 있음)를 계산하는 방법을 보여줍니다.

응답 한 많은 사람들은 동일한 알고리즘의 스택 사용량이 적은 대안 구현을 고려하는 것이 안전하고 좋은 코딩 관행이라고 지적했습니다. 일반적으로 반복 함수 집합을 반복 함수로 변환 할 수 있습니다 (예 : Stack런타임 스택 대신 힙에 채워지는 객체 사용 ). 이 특정 fact기능의 경우 변환하기가 매우 쉽습니다. 내 반복 버전은 다음과 같습니다.

public class TTIterative {
  public static long fact(int n) {
    if (n < 2) return 1;
    if (n > 65) return 0;  // Enough powers of 2 in the product to make it (long)0.
    long f = 2;
    for (int i = 3; i <= n; ++i) {
      f *= i;
    }
    return f;
  }
  public static void main(String[] args) {
    System.out.println(fact(1 << 15));
  }
}

참고로, 위의 반복 솔루션 fact에서 알 수 있듯이 Java 내장 유형 long이 오버플로 되기 때문에 함수는 65 이상의 숫자 (실제로는 20 이상)의 정확한 계승을 계산할 수 없습니다 . 대신 facta BigInteger를 반환하도록 리팩토링 하면 long큰 입력에 대해서도 정확한 결과를 얻을 수 있습니다.



답변

흠 … 저에게 효과적이며 999MB 미만의 스택으로 작동합니다.

> java -Xss4m Test
0

(Windows JDK 7, 빌드 17.0-b05 클라이언트 VM 및 Linux JDK 6-게시 한 것과 동일한 버전 정보)


답변

스택 추적에서 반복되는 라인으로 “1024 깊이”를 계산했다고 가정합니다.

분명히 Throwable의 스택 추적 배열 길이는 1024로 제한되어 있습니다. 다음 프로그램을 시도해보십시오.

public class Test {

    public static void main(String[] args) {

        try {
            System.out.println(fact(1 << 15));
        }
        catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was " +
                               e.getStackTrace().length);
        }
    }

    private static int level = 0;
    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }
}


답변

스레드 스택 크기를 사용하려면 핫스팟 JVM에서 -Xss 옵션을 확인해야합니다. JVM에 대한 -X 매개 변수가 배포에 따라 다르기 때문에 비 핫스팟 VM에서는 다른 것일 수 있습니다. IIRC.

핫스팟에서는 java -Xss16M크기를 16 메가로 만들고 싶을 때 처럼 보입니다 .

java -X -help전달할 수있는 배포 별 JVM 매개 변수를 모두 보려면 입력 하십시오. 이것이 다른 JVM에서 동일하게 작동하는지 확실하지 않지만 모든 핫스팟 특정 매개 변수를 인쇄합니다.

그만한 가치가 있습니다-Java에서 재귀 메서드 사용을 제한하는 것이 좋습니다. JVM이 꼬리 재귀를 지원하지 않는 경우 (JVM이 꼬리 호출 최적화를 방지합니까? 참조 ) 재귀 적 메서드 호출 대신 while 루프를 사용하려면 위의 팩토리얼 코드를 리팩토링 해보십시오.


답변

프로세스 내에서 스택 크기를 제어하는 ​​유일한 방법은 새로 시작하는 것 Thread입니다. 그러나 -Xss매개 변수를 사용하여 자체 호출 하위 Java 프로세스를 생성하여 제어 할 수도 있습니다 .

public class TT {
    private static int level = 0;

    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(null, null, "TT", 1000000) {
            @Override
            public void run() {
                try {
                    level = 0;
                    System.out.println(fact(1 << 15));
                } catch (StackOverflowError e) {
                    System.err.println("true recursion level was " + level);
                    System.err.println("reported recursion level was "
                            + e.getStackTrace().length);
                }
            }

        };
        t.start();
        t.join();
        try {
            level = 0;
            System.out.println(fact(1 << 15));
        } catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was "
                    + e.getStackTrace().length);
        }
    }

}


답변

이 옵션 추가

--driver-java-options -Xss512m

spark-submit 명령으로이 문제를 해결할 수 있습니다.


답변

모든 건전한 접근 방식을 피하고 싶기 때문에 현명한 해결책을 제시하는 것은 어렵습니다. 한 줄의 코드를 리팩토링하는 것이 현명한 해결책입니다.

참고 : -Xss를 사용하면 모든 스레드의 스택 크기가 설정되며 이는 매우 나쁜 생각입니다.

또 다른 접근 방식은 다음과 같이 코드를 변경하는 바이트 코드 조작입니다.

public static long fact(int n) {
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1);
}

n> 127에 대한 모든 대답은 0입니다. 이렇게하면 소스 코드를 변경하지 않습니다.


답변

기묘한! 당신은 1 << 15 깊이재귀 를 생성하고 싶다고 말하는 것입니다 ??? !!!!

나는 그것을 시도하지 않는 것이 좋습니다. 스택의 크기는입니다 2^15 * sizeof(stack-frame). 스택 프레임 크기는 모르겠지만 2 ^ 15는 32.768입니다. 꽤 많이 … 음, 1024 (2 ^ 10)에서 멈 추면 2 ^ 5 배 더 크게 만들어야합니다. 실제 설정보다 32 배 더 커야합니다.