관리 메뉴

The Nirsa Way

[JAVA] 객체 수명과 약한 세대 가설: 힙을 세대로 나누는 이유 본문

Development/JAVA

[JAVA] 객체 수명과 약한 세대 가설: 힙을 세대로 나누는 이유

KoreaNirsa 2026. 7. 25. 15:59
반응형
객체 수명과 약한 세대 가설: 힙을 세대로 나누는 이유

자바의 가비지 컬렉션을 공부하다 보면 다음과 같은 용어가 반복해서 등장합니다.

Eden 영역
Survivor 영역
Old Generation
Minor GC
객체 승격

힙을 여러 영역으로 나누는 이유는 객체마다 살아남는 시간이 다르기 때문입니다.

어떤 객체는 생성된 직후 잠깐 사용되고 사라집니다. 반면 애플리케이션이 실행되는 동안 계속 참조되면서 오랫동안 살아남는 객체도 있습니다.

public void process() {
    String message = "임시 메시지";
    User user = userRepository.findById(1L);
}

위 코드에서 message처럼 메서드 내부에서 잠깐 사용하는 객체는 비교적 빠르게 필요 없어질 수 있습니다.

반면 캐시, 설정 정보, 장시간 유지되는 세션 데이터처럼 여러 요청에서 계속 사용하는 객체는 오랫동안 살아남을 수 있습니다.

이러한 객체 수명의 차이를 가비지 컬렉션에 활용하는 대표적인 생각이 약한 세대 가설(Weak Generational Hypothesis)입니다.

  1. 객체가 얼마나 오래 살아남을지는 생성 시점에 정확히 알기 어렵습니다.
  2. 일반적으로 많은 객체는 생성된 뒤 비교적 빠르게 더 이상 사용되지 않는 경향이 있습니다.
  3. 새 객체와 오래 살아남은 객체를 서로 다른 영역에서 관리하면 가비지 컬렉션의 처리 범위와 비용을 줄일 수 있습니다.
  4. 전통적인 세대별 힙 구조에서는 새 객체가 Eden에서 시작하고, 살아남은 객체가 Survivor를 거쳐 Old Generation으로 이동합니다.

먼저 알아둘 가비지 컬렉션 용어

세대별 가비지 컬렉션을 이해하려면 객체를 저장하고 회수하는 과정에서 자주 사용하는 몇 가지 용어를 먼저 정리해야 합니다.

용어 의미
할당 새 객체가 사용할 메모리 공간을 확보하는 작업입니다.
회수 더 이상 사용할 수 없는 객체가 차지한 메모리를 재사용할 수 있게 만드는 작업입니다.
병렬 처리 여러 GC 스레드가 동시에 수집 작업을 수행하는 방식입니다.
동시 처리 애플리케이션 스레드와 GC 스레드가 일부 작업을 함께 진행하는 방식입니다.
정확한 GC 어떤 값이 객체 참조인지 JVM이 정확하게 구분할 수 있는 방식입니다.
보수적 GC 메모리의 값이 참조인지 확실하지 않으면 참조일 가능성이 있다고 판단하는 방식입니다.
객체 이동 살아 있는 객체를 다른 메모리 위치로 옮기는 작업입니다.
압축 살아 있는 객체를 모아 연속된 빈 공간을 확보하는 작업입니다.
승격 젊은 세대에서 오래 살아남은 객체를 오래된 세대로 이동시키는 작업입니다.

할당과 회수는 반대 방향의 작업입니다.

객체를 생성할 때는 메모리를 할당합니다. 가비지 컬렉션이 더 이상 사용할 수 없는 객체를 처리할 때는 객체가 차지한 메모리를 회수합니다.

할당
→ 새 객체가 사용할 공간을 확보합니다.

회수
→ 사용이 끝난 객체의 공간을 다시 사용할 수 있게 만듭니다.

 


Stop-The-World란

가비지 컬렉션을 설명할 때 자주 등장하는 개념이 Stop-The-World, STW입니다.

STW는 가비지 컬렉션 작업을 수행하기 위해 JVM이 애플리케이션 스레드의 실행을 일시적으로 멈추는 상태를 말합니다.

GC가 객체의 참조 관계를 조사하는 동안 애플리케이션이 객체를 계속 생성하거나 참조 관계를 변경하면, 수집기가 확인하는 객체 그래프가 계속 달라질 수 있습니다.

따라서 일부 GC 단계에서는 애플리케이션 스레드를 멈추고 객체 상태를 안정적으로 확인해야 합니다.

다만 모든 가비지 컬렉터가 전체 수집 작업을 STW 상태에서만 진행하는 것은 아닙니다. 수집기에 따라 일부 작업은 애플리케이션과 동시에 수행하고, 반드시 필요한 특정 단계에서만 짧은 STW를 발생시키기도 합니다.

STW가 발생하면 해당 시간 동안 애플리케이션은 사용자 요청을 처리하지 못할 수 있습니다.

STW 시간 증가
    ↓
요청 처리 지연
    ↓
응답 시간 증가
    ↓
사용자가 멈춤 현상을 느낄 수 있음

가비지 컬렉터를 선택하거나 힙 크기를 조정할 때는 단순히 메모리를 얼마나 많이 회수하는지만 보는 것이 아닙니다.

다음 요소의 균형을 함께 고려해야 합니다.

기준 의미
처리량 전체 실행 시간 중 애플리케이션이 실제 작업을 수행한 비율
일시 정지 시간 한 번의 STW가 지속되는 시간
GC 발생 빈도 가비지 컬렉션이 얼마나 자주 발생하는지
메모리 사용량 가비지 컬렉터가 동작하는 데 필요한 추가 메모리

 


이동과 압축은 왜 같이 등장할까

가비지 컬렉션은 가비지 객체의 공간만 비워 두는 방식으로 동작할 수도 있고, 살아남은 객체를 다른 위치로 옮길 수도 있습니다.

객체가 이동하면 해당 객체를 가리키던 참조도 새로운 위치를 가리키도록 갱신해야 합니다.

압축은 살아 있는 객체를 한쪽으로 모아 빈 공간을 연속되게 만드는 처리입니다.

압축 전

[객체 A][빈 공간][객체 B][빈 공간][객체 C]

압축 후

[객체 A][객체 B][객체 C][연속된 빈 공간]

객체 이동과 압축은 메모리 단편화를 줄이고 새로운 객체를 할당하기 쉬운 상태를 만드는 데 도움이 됩니다.

하지만 객체를 이동하고 참조를 갱신하는 작업에는 추가 비용이 발생합니다.


객체는 얼마나 오래 살아남을까

객체는 생성된 뒤 서로 다른 수명을 가집니다.

다음과 같이 메서드 내부에서 잠깐 사용하는 객체가 있을 수 있습니다.

public String createMessage(String name) {
    String prefix = "안녕하세요, ";
    String message = prefix + name;

    return message;
}

prefix와 문자열 결합 과정에서 생성된 일부 객체는 메서드 실행이 끝난 뒤 빠르게 접근할 수 없는 상태가 될 수 있습니다.

반면 다음과 같이 애플리케이션 실행 중 계속 유지되는 객체도 있습니다.

public class ApplicationConfig {

    private static final ApplicationConfig INSTANCE =
            new ApplicationConfig();
}

정적 필드에서 계속 참조되는 ApplicationConfig 객체는 장시간 살아남을 가능성이 큽니다.

문제는 JVM이 객체를 생성하는 순간에는 해당 객체가 얼마나 오래 살아남을지 정확히 알기 어렵다는 점입니다.

새 객체 생성
    ↓
짧게 살지 오래 살지 아직 알 수 없음
    ↓
GC를 거치며 실제 생존 여부 확인
    ↓
계속 살아남으면 장수 객체로 판단

따라서 객체가 실제로 몇 번의 가비지 컬렉션을 통과했는지를 바탕으로 관리 영역을 구분하는 방식이 사용됩니다.


약한 세대 가설이란

약한 세대 가설(Weak Generational Hypothesis)은 객체의 생존 특성에 관한 경험적 관찰입니다.

일반적으로 다음과 같은 두 가지 경향을 중심으로 설명합니다.

  1. 대부분의 객체는 생성된 뒤 비교적 빠르게 사용이 끝납니다.
  2. 일정 시간 동안 살아남은 객체는 이후에도 계속 살아남을 가능성이 상대적으로 높습니다.

첫 번째 경향은 대부분의 객체가 젊을 때 죽는다는 의미입니다.

두 번째 경향은 여러 번의 가비지 컬렉션을 통과한 객체가 앞으로도 계속 사용될 가능성이 높다는 의미입니다.

여기서 주의할 점은 모든 객체가 반드시 짧게 산다는 의미가 아니라는 것입니다.

약한 세대 가설은 객체의 수명 분포에서 짧게 살아남는 객체가 많이 나타나는 경향이 있다는 설명입니다.

 

왜 ‘약한’ 세대 가설일까

모든 프로그램과 모든 객체에 절대적으로 적용되는 법칙이 아니기 때문입니다.

애플리케이션의 구조와 사용 방식에 따라 오래 살아남는 객체가 많이 생성될 수도 있습니다.

예를 들어 다음과 같은 객체는 비교적 오래 살아남을 수 있습니다.

  • 애플리케이션 설정 객체
  • 캐시에 저장된 객체
  • 장시간 유지되는 세션 객체
  • 스레드 풀과 커넥션 풀
  • 애플리케이션 전역에서 사용하는 싱글턴 객체

따라서 약한 세대 가설은 절대적인 규칙이라기보다, 여러 프로그램에서 관찰되는 객체 생존 경향을 가비지 컬렉션 설계에 활용한 것입니다.


힙을 세대로 나누는 이유

모든 객체를 하나의 영역에서 동일한 방식으로 처리한다고 가정해 보겠습니다.

가비지 컬렉션이 발생할 때마다 힙 전체를 탐색해야 한다면, 짧게 사용되는 객체 몇 개를 회수하기 위해 오래 살아남은 객체까지 반복해서 확인해야 할 수 있습니다.

하지만 새로 생성된 객체가 모여 있는 영역과 오래 살아남은 객체가 모여 있는 영역을 나누면 상황이 달라집니다.

세대를 나누면 가비지 컬렉터는 짧은 수명의 객체가 집중되는 영역을 우선적으로 수집할 수 있습니다.

힙 전체를 매번 검사하지 않고, 가비지가 많이 발생할 가능성이 높은 영역을 중심으로 처리할 수 있는 것입니다.

새 객체가 많이 생성되는 영역
→ 가비지가 많이 발생할 가능성이 높음
→ 비교적 자주 수집

오래 살아남은 객체가 모인 영역
→ 가비지가 될 가능성이 상대적으로 낮음
→ 젊은 세대와 다른 주기로 수집

전통적인 세대별 힙 구조

전통적인 세대별 가비지 컬렉션에서는 힙을 크게 다음과 같이 구분합니다.

Young Generation
    ├─ Eden
    ├─ Survivor 0
    └─ Survivor 1

Old Generation

위 흐름도는 객체의 개념적인 이동 방향을 단순화한 것입니다.

실제 Survivor 영역은 일반적으로 두 공간 중 하나를 비워 둔 상태에서, GC가 발생할 때 살아남은 객체를 다른 Survivor 영역으로 복사하는 방식으로 사용됩니다.

세대 또는 영역 역할
Eden 대부분의 새 객체가 처음 할당되는 영역입니다.
Survivor Young GC에서 살아남은 객체가 이동하는 영역입니다.
Old Generation 여러 번의 GC에서 살아남은 객체가 이동하는 영역입니다.

새 객체는 Eden에서 시작합니다

일반적인 세대별 힙 구조에서는 새 객체가 먼저 Eden 영역에 할당됩니다.

User user = new User();
Order order = new Order();

개념적으로 다음과 같이 객체가 Eden에 쌓입니다.

Eden 영역은 새로운 객체가 지속적으로 생성되는 공간입니다.

객체 생성
    ↓
Eden에 할당
    ↓
Eden 사용량 증가
    ↓
Eden에 공간 부족
    ↓
Young Generation을 대상으로 GC 발생

대부분의 새 객체가 빠르게 가비지 객체가 된다는 가정이 맞는다면, Eden을 수집할 때 많은 객체를 한 번에 제거할 수 있습니다.


살아남은 객체는 Survivor로 이동합니다

Young Generation을 대상으로 가비지 컬렉션이 발생하면, Eden에 있는 객체 중 GC Root에서 도달할 수 있는 객체가 살아남습니다. 살아남은 객체는 Survivor 영역으로 이동할 수 있습니다.

이 과정에서 가비지 객체는 제거되고, 살아남은 객체만 다음 영역으로 복사됩니다.

Eden의 객체
    ↓
Young GC 실행
    ↓
살아 있는 객체 확인
    ↓
생존 객체는 Survivor로 이동
    ↓
가비지 객체의 공간은 회수

 


Survivor 영역은 왜 두 개일까

Young Generation에는 일반적으로 두 개의 Survivor 영역이 있습니다.

보통 다음과 같이 표현합니다.

Survivor 0
Survivor 1

또는 현재 역할에 따라 다음과 같이 부릅니다.

From Survivor
To Survivor

Young GC가 발생하면 Eden과 현재 사용 중인 Survivor 영역의 살아 있는 객체를 비어 있는 Survivor 영역으로 복사합니다.

예를 들어 첫 번째 GC에서 Survivor 0으로 객체를 복사했다면, 다음 GC에서는 Survivor 0의 생존 객체를 Survivor 1로 이동할 수 있습니다.

두 Survivor 영역을 번갈아 사용하면 살아 있는 객체만 모아서 복사할 수 있습니다.

이 과정에서 빈 공간이 자연스럽게 연속적으로 확보되므로, 젊은 세대에서는 별도의 복잡한 압축 작업을 줄일 수 있습니다.


객체의 나이는 무엇일까

Survivor 영역에서 살아남은 객체에는 객체 나이(Age)라는 개념이 사용됩니다.

객체 나이는 실제 시간이나 초 단위를 의미하지 않으며 객체가 Young GC에서 살아남은 횟수와 관련된 값입니다.

객체 생성
→ 나이 0

첫 번째 Young GC 생존
→ 나이 증가

두 번째 Young GC 생존
→ 나이 다시 증가

계속 생존
→ 승격 후보

객체가 여러 차례 가비지 컬렉션을 통과했다는 것은 해당 객체가 잠깐 사용되는 임시 객체가 아닐 가능성이 높다는 의미입니다.

따라서 일정 기준 이상 살아남은 객체는 Old Generation으로 이동할 수 있습니다.


승격이란

승격(Promotion)은 Young Generation에서 살아남은 객체가 Old Generation으로 이동하는 것을 말합니다.

일반적으로 객체가 일정 나이에 도달하면 승격될 수 있습니다만, 실제 승격 시점은 단순히 객체 나이 하나로만 결정되지 않을 수 있습니다.

다음 조건도 영향을 줄 수 있습니다.

  • Survivor 영역의 사용 가능한 공간
  • 살아남은 객체의 전체 크기
  • 객체의 나이 분포
  • 수집기 종류와 JVM 설정
  • 매우 큰 객체의 크기

Survivor 영역에 객체를 모두 저장할 수 없다면, 기준 나이에 도달하지 않은 객체도 Old Generation으로 이동할 수 있습니다.

Young GC에서 객체 생존
    ↓
Survivor 공간 확인
    ↓
공간 충분
→ Survivor로 이동

공간 부족
→ 일부 객체가 Old Generation으로 승격될 수 있음

 


객체의 전체 이동 흐름

전통적인 세대별 힙 구조에서 객체의 생애를 정리하면 다음과 같습니다.

이를 간단하게 표현하면 다음과 같습니다.

객체 생성
    ↓
Eden
    ↓
Young GC 생존
    ↓
Survivor
    ↓
여러 번 생존
    ↓
Old Generation

모든 객체가 이 경로를 반드시 그대로 거치는 것은 아닙니다. 수집기와 객체 크기, 메모리 상황에 따라 객체가 Survivor를 충분히 거치지 않고 Old Generation으로 이동할 수도 있습니다.


세대를 나누면 무엇이 달라질까

세대를 나누는 가장 큰 이유는 수집 대상을 객체의 생존 특성에 따라 구분하기 위해서입니다.

 

세대를 나누지 않은 경우

 

세대를 나눈 경우

짧게 살아남는 객체가 많은 Young Generation을 우선 수집하면 비교적 작은 범위를 대상으로 많은 가비지를 회수할 수 있습니다.

 

세대 구분의 효과
효과 설명
수집 범위 축소 매번 전체 힙을 모두 탐색하지 않아도 됩니다.
빠른 메모리 회수 짧게 사는 객체가 많은 영역을 집중적으로 수집할 수 있습니다.
객체 특성별 관리 젊은 객체와 오래된 객체에 서로 다른 방식을 적용할 수 있습니다.
전체 GC 부담 감소 오래 살아남은 객체를 매번 반복해서 처리하는 일을 줄일 수 있습니다.

 

Young Generation이 작으면 무조건 좋을까

Young Generation이 작으면 한 번에 검사해야 하는 영역이 줄어들 수 있습니다. 따라서 한 번의 Young GC 정지 시간이 짧아질 가능성이 있습니다.

하지만 공간이 빠르게 가득 차기 때문에 GC가 더 자주 발생할 수 있습니다.

Young Generation이 작음
    ↓
빠르게 가득 참
    ↓
Young GC가 자주 발생할 수 있음
    ↓
한 번의 정지는 짧아도 전체 GC 횟수 증가 가능

반대로 Young Generation이 크면 GC 발생 빈도는 줄어들 수 있지만, 한 번 수집할 때 처리해야 할 객체의 수가 많아질 수 있습니다.

Young Generation 크기 장점 고려할 점
작은 경우 한 번의 수집 범위가 작을 수 있습니다. GC가 자주 발생할 수 있습니다.
큰 경우 객체를 더 많이 할당한 뒤 GC가 발생합니다. 한 번의 수집 시간이 길어질 수 있습니다.

따라서 Young Generation은 무조건 크거나 작게 만드는 것이 아니라, 애플리케이션의 객체 할당 속도와 응답 시간 요구사항에 맞게 조정해야 합니다.


Old Generation의 수집 부담이 큰 이유

Old Generation에는 여러 번의 가비지 컬렉션을 통과한 객체가 모입니다. 따라서 Young Generation과 비교하면 살아 있는 객체의 비율이 높을 가능성이 있습니다.

가비지 컬렉션은 가비지 객체만 찾는 것이 아니라 살아 있는 객체도 확인해야 합니다.

Old Generation에 많은 객체가 존재하고 그중 상당수가 살아 있다면, 객체를 탐색하고 표시하거나 이동하는 비용이 커질 수 있습니다.

Old Generation 객체 증가
    ↓
탐색해야 할 참조 증가
    ↓
살아 있는 객체 처리 비용 증가
    ↓
GC 부담과 정지 시간 증가 가능

따라서 오래된 세대를 수집하는 작업은 일반적으로 Young Generation만 수집하는 작업보다 부담이 클 수 있습니다.


모든 가비지 컬렉터가 같은 구조를 사용할까

Eden, 두 개의 Survivor 영역, Old Generation으로 나누는 구조는 세대별 가비지 컬렉션을 이해하는 대표적인 모델입니다.

하지만 실제 힙의 구조와 객체 이동 방식은 사용하는 가비지 컬렉터에 따라 달라질 수 있으며 요소는 아래와 같습니다.

  • 힙을 나누는 단위
  • 객체가 이동하는 방식
  • Young 영역의 구성
  • Old 영역을 수집하는 방식
  • 병렬 또는 동시 처리 방식
  • 승격 기준

예를 들어 어떤 수집기는 메모리를 물리적으로 연속된 Eden과 Old 영역으로 고정하지 않고, 여러 개의 작은 리전 단위로 나누어 역할을 부여하기도 합니다.

따라서 다음과 같이 이해하는 것이 좋습니다.

Eden → Survivor → Old

객체의 세대별 이동 원리를 이해하기 위한 대표적인 모델

세부 구현은 수집기마다 달라질 수 있지만, 짧게 사는 객체와 오래 사는 객체를 구분해서 관리한다는 기본 목적은 같습니다.


약한 세대 가설과 세대별 힙의 관계

약한 세대 가설이 실제 힙 구조에 어떻게 반영되는지 정리하면 다음과 같습니다.

 

약한 세대 가설의 관찰 힙 구조에 반영된 방식
새 객체 대부분은 빠르게 사라집니다. 새 객체를 Young Generation에 모읍니다.
일부 객체만 GC에서 살아남습니다. 살아남은 객체를 Survivor로 이동합니다.
여러 번 살아남은 객체는 계속 생존할 가능성이 높습니다. 일정 조건을 만족하면 Old Generation으로 승격합니다.
오래된 객체를 매번 검사하는 것은 비효율적입니다. Young과 Old를 구분해 서로 다른 주기로 수집합니다.

핵심 정리
  • 객체 할당은 새 객체가 사용할 메모리 공간을 확보하는 작업입니다.
  • 메모리 회수는 가비지 객체가 차지한 공간을 다시 사용할 수 있게 만드는 작업입니다.
  • STW는 일부 GC 작업을 위해 애플리케이션 스레드가 일시적으로 멈추는 상태입니다.
  • 객체의 실제 수명은 생성 시점에 정확히 알기 어렵습니다.
  • 약한 세대 가설은 많은 객체가 생성된 뒤 비교적 빠르게 사라진다는 관찰입니다.
  • 여러 번의 GC에서 살아남은 객체는 이후에도 계속 살아남을 가능성이 상대적으로 높습니다.
  • 전통적인 세대별 힙에서는 새 객체가 Eden에 할당됩니다.
  • Young GC에서 살아남은 객체는 Survivor 영역으로 이동합니다.
  • Survivor에서 계속 살아남은 객체는 Old Generation으로 승격될 수 있습니다.
  • 객체 나이는 실제 시간이 아니라 Young GC에서 살아남은 횟수와 관련된 값입니다.
  • 세대를 나누면 매번 전체 힙을 수집하지 않고 객체 수명 특성에 따라 다른 방식으로 관리할 수 있습니다.
  • 실제 힙 구조와 객체 이동 방식은 가비지 컬렉터에 따라 달라질 수 있습니다.

마무리

힙을 세대로 나누는 이유는 모든 객체의 수명이 같지 않기 때문입니다.

많은 객체는 생성된 뒤 잠깐 사용되고 빠르게 접근할 수 없는 상태가 됩니다. 반면 일부 객체는 여러 번의 가비지 컬렉션을 통과하며 오랫동안 살아남습니다.

세대별 가비지 컬렉션은 이러한 차이를 이용합니다.

짧게 살아남는 객체는 Young Generation에서 빠르게 회수하고, 여러 번 살아남은 객체는 Old Generation으로 이동시켜 별도로 관리합니다.

이것이 약한 세대 가설이 실제 가비지 컬렉션 구조에 반영된 대표적인 방식입니다.

반응형