7. JVM 과 GC 그리고 객체

1. 세대별 컬렉션 이론

세대 단위 컬렉션 이론 (몇가지 가설을 참고한다)

  • 대다수 객체는 일찍 사라짐, 참조가 사용되지 않음. (약한 세대 가설)

    • Eden 영역에서 대부분 사라질 것이다!

  • GC 과정에서 살아남은 회수가 늘어날 수록 (앞으로도) 생존 가능성이 높아짐. (강한 세대 가설)

  • 다른 세대에 속한 객체간 참조는 같은 세대에 속한 객체간 참조보다 훨씬 적음 (세대 간 참조 가설)

  • Hotspot VM 에서 에덴과 생존자 공간비율은 보통 8:1

    • IBM 연구에 따름 (보통 첫 GC 에 대략 98% 객체가 소멸)

    • 파편화 방지를 위해 활용 Survivor space 사용

JVM heap 영역 (상세)

🟦 Young Generation (신생 세대)

  • Eden + Survivor(S0, S1) 공간으로 구성됨

  • 새로 생성된 객체 대부분은 Eden 영역에 먼저 할당

  • Minor GC가 발생하면 Eden + 하나의 Survivor를 청소하고, 살아남은 객체는 다른 Survivor 또는 Old Generation으로 "승격(Promotion)"

▸ Eden

  • 객체가 최초로 할당되는 공간

  • GC가 자주 일어나고, 객체 생명주기가 짧음

▸ Survivor space (S0, S1)

  • GC 시 복사-이동 대상이 되는 공간

  • S0와 S1은 번갈아가며 사용됨 (from-space, to-space)

  • 여러 번 살아남은 객체는 Old Generation으로 승격

  • age bit 사용 (참조계수) -> 클래스 메타데이터에 포함


🟦 Old Generation (Tenured)

  • 장기 생존 객체가 저장되는 공간

  • Minor GC에선 건드리지 않으며, Major GC나 Full GC 시에 청소됨

  • Full GC는 이 영역이 꽉 찼거나, Promotion 대상이 들어갈 공간이 없을 때 발생

2. 대표적 GC 알고리즘(Mark, Sweep, Compact)

🟩 1. Mark and Sweep

📆 1960년대 초 John McCarthy 제안 (LISP GC의 기초)

🔧 작동 방식

  1. Mark 단계

    • GC Root(스택, static 변수 등)에서 도달 가능한 객체를 표시(mark)

    • 이때 방문한 객체에 "mark bit"를 세팅함

  2. Sweep 단계

    • 힙 전체를 선형 탐색하며, mark되지 않은(dead) 객체를 회수(free)

    • 단, 메모리 정리는 안 함 → 단편화 발생

⚠️ 단점

  • 파편화 발생: 객체가 삭제돼도 자리는 남아있어 조각난 공간이 생김

  • 연속 공간 확보 어려움 → 큰 객체 할당 실패

  • STW 시간 길어짐: 전체 Heap을 선형으로 탐색하기 때문

🧠 JVM 적용

  • 현대 JVM에서는 단독으로 사용되지 않음

  • 하지만 기본 개념이 CMS, G1, ZGC 등 거의 모든 GC에 내재


🟩 2. Mark and Copy

📆 1974년 Robert Fenichel & Peter Bishop 제안 → Copying Collector라고도 불림

🔧 작동 방식

  • Heap을 두 영역으로 나눔 (From / To Space)

  • 객체는 항상 From 영역에 생성

  • GC 시:

    • 살아있는 객체만 To 영역에 복사

    • 참조 포인터를 모두 새 객체로 갱신

  • GC 후, From ↔ To 역할을 스왑

⚠️ 단점

  • 메모리 낭비: 절반은 항상 비어 있어야 → 유효 메모리는 전체 Heap의 50% 미만

  • 복사 비용 존재: 살아있는 객체를 이동해야 함

📌 JVM 적용

  • Young 영역 (Eden + Survivor)에서 사용

  • Survivor 영역이 2개 (S0, S1): 교대로 사용되며, Eden:S0:S1 비율은 기본 8:1:1

  • 객체 생존율이 낮은 Young 영역에 최적화된 방식


🟩 3. Mark and Compact

📆 1970년대 중반 Edward L. Ruders 등 제안 → Mark-Sweep의 파편화 문제 + Copy의 메모리 낭비를 함께 해결

🔧 작동 방식

  1. Mark 단계

    • 도달 가능한 객체에 mark bit 설정

  2. Compact 단계

    • 힙 앞쪽으로 객체들을 밀어넣으며 정렬 압축

    • 이후 공간은 free로 남김 → 연속 공간 확보

⚠️ 단점

  • 객체가 많을수록 이동 비용 커짐

  • 포인터 갱신 비용: 객체 주소가 바뀌므로 참조하는 곳도 모두 변경

  • STW가 발생함 (전체 압축 동안 애플리케이션 멈춤)

📌 JVM 적용

  • 주로 Old 영역에서 사용

  • 대표적으로 Parallel Old GC, G1 GC의 Old Region, Serial Old GC에서 사용됨

3. 도달 가능성 분석

도달 가능성 분석

  • 도달 가능성 분석(Reachability analysis) 알고리즘에 기초해 회수 대상 인스턴스를 판단

  • GC 루트 객체들을 기점으로 참조하는 다른 객체들을 탐색하는 방식

  • 힙 메모리가 크다보면 Full GC 가 났을 때 도달 가능성 분석 시간이 너무 오래 걸릴 수 있기 때문에, 힙 메모리를 늘리는 것이 좋은가? 는 한번 더 생각해 볼 필요가 있다.

GC 루트 객체가 될 수 있는 것들

  • JVM stack frame 지역변수 테이블에서 참조하는 객체 (현재 실행 중인 메서드)

  • Synchronized 키워드로 잠겨 있는(모니터 락) 객체

  • JNI 가 참조하는 객체

  • 메서드 영역에서 클래스 정적 필드로 참조하는 객체

  • 메서드 영역에서 상수로 참조되는 객체

  • JVM 내부에서 사용되는 참조

4. G1(Garbage First) GC

Stop-The-World(STW) ?

  • Stop-The-World(STW)는 GC가 실행될 때 모든 애플리케이션 스레드를 멈추는 순간입니다.

  • 왜 멈추냐면? 👉 GC는 메모리 객체를 스캔하면서 참조 그래프를 따라 이동하는데, 그 사이 애플리케이션이 객체를 수정하면 무결성이 깨지기 때문입니다.

클래식 GC 종류

GC 종류
동작 방식
멀티스레드
주요 목적
현재 상태

Serial

STW + 단일 스레드

단순성, 저자원

유지

ParNew

STW + Young 병렬

CMS 보조용

CMS 제거와 함께 사라지는 중

Parallel (Scavenge)

STW + Young/Old 병렬

Throughput 극대화

유지

CMS

Old 영역 병행 수집

✅ (일부 STW)

정지시간 최소화

제거됨 (Java 14)

G1

Region 기반 수집

Predictable pause time

기본 GC

📘 1. Serial GC

  • Java에서 가장 기본적인 GC 방식입니다.

  • Young과 Old 영역 모두 단일 스레드로 GC 작업을 수행합니다.

  • GC가 실행되면 전체 애플리케이션이 Stop-The-World(STW) 상태에 진입합니다.

  • Young 영역은 Mark and Copy, Old 영역은 Mark and Compact 알고리즘을 사용합니다.

  • 구현이 매우 단순하고 리소스 사용량이 적기 때문에, 임베디드 환경이나 테스트용 소형 애플리케이션에 적합합니다.

  • 병렬/병행 처리가 불가능하므로, Heap 크기가 커질수록 GC 정지 시간이 급격히 증가합니다.

🧱 특징:

  • 구조가 단순하여 이해와 디버깅이 쉬움

  • 작은 Heap에서 효율적

  • STW 시간이 비교적 길어질 수 있음

  • 병렬 GC 기능이 없어 서버 환경에는 적합하지 않음


📘 2. ParNew GC

  • Serial GC의 Young 영역 수집을 멀티스레드화한 GC입니다.

  • Young 영역만 병렬 수집하며, Old 영역은 CMS 또는 Serial Old와 함께 처리됩니다.

  • 단독 사용보다는 CMS GC의 Young 영역 수집기로 주로 사용됩니다.

  • GC 중 일부 STW가 발생하지만, Serial GC보다는 정지 시간이 짧아집니다.

  • CMS와 조합 시 병렬 Young 수집 + 병행 Old 수집 구조로 활용됩니다.

🧱 특징:

  • Young 영역에서 병렬 수집을 지원

  • Old 영역은 여전히 CMS나 Serial Old에 의존

  • CMS 기반 시스템에서 Young GC 성능을 향상시킴

  • 완전한 병렬 GC는 아님 (Old 영역은 병렬화되지 않음)


📘 3. Parallel GC (Parallel Scavenge)

  • 전체 처리량(Throughput)을 극대화하기 위해 설계된 GC입니다.

  • Young 영역과 Old 영역 모두를 멀티스레드로 병렬 수집합니다.

  • Young 영역은 Parallel Scavenge, Old 영역은 Parallel Old 알고리즘을 사용합니다.

  • GC 중에는 STW가 발생하지만, 작업을 병렬로 수행하므로 총 시간은 짧아질 수 있습니다.

  • Throughput이 중요한 배치 처리, 대용량 연산 중심의 서버 환경에서 적합합니다.

🧱 특징:

  • Young + Old 영역 모두 병렬 처리 가능

  • Throughput 중심 애플리케이션에 최적화

  • GC 시간 자체는 길 수 있으나, 애플리케이션 전체 처리 효율은 높음

  • 지연시간이 중요한 서비스에는 적합하지 않을 수 있음


📘 4. CMS (Concurrent Mark Sweep) GC

  • GC로 인한 정지 시간(STW)을 최소화하기 위해 도입된 GC입니다.

  • Old 영역의 수집을 병행(Concurrent)으로 수행하며, 일부 단계에서만 STW가 발생합니다.

  • 수집 단계는 Initial Mark(STW) → Concurrent Mark → Remark(STW) → Concurrent Sweep 순으로 진행됩니다.

  • Old 영역에서는 Compaction을 수행하지 않기 때문에 메모리 단편화가 발생할 수 있습니다.

  • 이로 인해 할당 실패(Allocation Failure)나 OOM 가능성이 존재합니다.

  • Java 9에서 deprecated, Java 14에서 완전히 제거되었습니다.

🧱 특징:

  • STW 시간을 줄이기 위한 병행 수집 방식

  • 메모리 단편화 발생 가능성

  • 실시간 응답이 중요한 시스템에 한때 많이 사용되었음

  • G1 GC의 등장으로 대체됨


📘 5. G1 GC (Garbage First GC)

  • CMS의 단점을 보완하기 위해 Java 7에 도입되었으며, Java 9부터 기본 GC로 채택되었습니다.

    • CMS 는 compaction 을 수행하지 않아서, 메모리 단편화로 인해 OOM 이 발생할 수 있다.

    • CMS는 대부분 병행으로 실행되지만, Initial Mark, Remark 단계는 여전히 STW, 이로인해 GC 발생시기나, STW 길이가 예측 불가하다.

  • 전체 Heap을 고정 크기의 Region 단위로 나누어 관리합니다.

  • 각 Region은 GC 시점마다 Eden, Survivor, Old 등의 역할이 동적으로 할당됩니다.

  • Young/Old 영역 구분 없이 가비지가 많은 Region을 우선적으로 수집합니다.

  • GC 정지 시간을 예측하기 위해 -XX:MaxGCPauseMillis 옵션으로 목표 시간을 설정할 수 있으며, 내부적으로 작업을 분산(GC Scheduling)하여 정지 시간을 예측 가능하게 조절합니다.

  • 대용량 Heap에서도 파편화 없이 연속 공간을 확보할 수 있습니다.

🧱 특징:

  • 4GB 이상 대용량 Heap에 적합

  • Region 기반 관리로 단편화 방지

  • Predictable pause time 설정 가능

  • CMS의 단점을 대부분 보완한 현대적 GC 방식

5. 객체(Java Object) 메모리 레이아웃과 해시코드

Object header

  • Mark word

    • Hash code

      • Hash code 는 Object.hashCode() 함수가 호출되는 시점에 계산

        • Hash code 는 연산에 비용이 들어가기 때문에, 실제 함수가 호출될 때만 계산한다.

      • Hash code 는 어떤 인스턴스에 대해서 Unique 하게 식별할 수 있는 값을 의미한다.

      • Native 언어는 주소가 있기 때문에, 해당 값이 필요하지 않을 수 있다..

      • 하지만 Java 는 JVM 이 주소를 관리하기 때문에, 개발자 입장에서 Unique 하게 식별할 수 있는 방법이 필요하다.

    • Object age

      • GC 에서 살아남은 횟수 (비트단위 데이터)

    • Lock flag

      • Lock flag 를 중심으로 멀티스레드 환경에서 경쟁조건이 발생하는 문제를 해결하기 위한 것

      • 모니터 락

  • Klass work

    • Class 예약어가 있기에 Klass 로 표현

    • Class meta data(Metaspace 영역) 의 레퍼런스

  • length(Array)

    • 배열임을 나타내는 정보

Hash(함수) 의 특징

f(x) -> y 만 가능한 단방향 함수

  • 단방향성 (이 특정을 사용해서 DB 에 메모리를 저장할 때 해시값을 저장)

  • 입력 값의 크기와 상관 없이 결과 값의 길이(혹은 크기) 가 일정 (Unique)

  • 데이터 무결성 확보와 관련해 IT 기술 전반(특히 보안)에서 사용된다.

  • 대표적 해시 알고리즘

    • MD-5, SHA-1

    • SHA-128, 256, 384, 512

Instance data

Last updated