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 이상)의 정확한 계승을 계산할 수 없습니다 . 대신 fact
a 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 배 더 커야합니다.