개발/Trouble Shooting

재귀의 늪에 빠지다: StackOverflowError 원인과 해결 방법

ophelisis 2025. 12. 29. 10:10
반응형

1. ❓ StackOverflowError란 무엇인가요?

자바에서 각 스레드는 자신만의 스택(Stack) 영역을 가집니다. 메서드가 호출될 때마다 해당 메서드의 정보(로컬 변수, 매개변수, 복귀 주소 등)가 '스택 프레임'이라는 이름으로 쌓이게 됩니다.

StackOverflowError는 이 스택 영역의 제한된 용량을 초과하여 더 이상 프레임을 쌓을 수 없을 때 JVM이 던지는 에러입니다.


2. 🔍 주요 발생 원인

이 에러가 발생하는 원인은 대부분 코드의 논리적 오류에 있습니다.

  • 무한 재귀(Infinite Recursion): 탈출 조건이 없는 재귀 함수가 자기 자신을 끝없이 호출할 때 발생합니다. (가장 흔한 원인)
  • 상호 참조(Circular Reference): A 클래스의 toString()이 B를 호출하고, B의 toString()이 다시 A를 호출하는 구조일 때 발생합니다.
  • 너무 깊은 메서드 호출: 재귀가 아니더라도 메서드 호출의 깊이(Depth)가 스택 크기보다 깊을 때 발생합니다.
  • 대규모 로컬 변수: 메서드 하나에서 너무 많은 로컬 변수를 선언하여 스택 프레임 하나가 너무 클 때 드물게 발생합니다.

3. 🛠️ 실전! 해결 및 예방 전략

✅ 1. 재귀 탈출 조건(Base Case) 확인

재귀 함수를 작성할 때는 반드시 "언제 멈출 것인가"를 명확히 해야 합니다.

Java
 
// 잘못된 예: 무한 재귀
public int factorial(int n) {
    return n * factorial(n - 1); // n이 0 이하일 때의 조건이 없음!
}

// 올바른 예
public int factorial(int n) {
    if (n <= 1) return 1; // 탈출 조건 명시
    return n * factorial(n - 1);
}

✅ 2. 반복문(Loop)으로 전환

메서드 호출 깊이가 너무 깊다면 재귀 대신 for나 while 문을 사용하는 것이 안전합니다. 반복문은 스택 프레임을 새로 생성하지 않기 때문에 메모리 효율적입니다.

✅ 3. 스택 크기 조절 (임시 방편)

정말로 깊은 호출이 필요한 로직이라면 JVM 옵션을 통해 스레드당 스택 크기를 키울 수 있습니다. (하지만 근본적인 해결책은 아닙니다.)

Bash
 
# 스택 크기를 2MB로 설정 (기본값은 보통 1MB 내외)
java -Xss2m -jar my-app.jar

💡 시니어의 조언: '꼬리 재귀 최적화'를 아시나요?

자바는 아쉽게도 언어 차원에서 **꼬리 재귀 최적화(Tail Call Optimization)**를 완벽히 지원하지 않습니다. 따라서 재귀 깊이가 수천 번 이상 깊어질 가능성이 있다면, 처음부터 스택 구조를 사용하는 반복문이나 함수형 프로그래밍의 스트림 처리를 고민하는 것이 '중급' 개발자의 센스입니다.

반응형