[Memory] 자바의 메모리 관리 전략

2026. 1. 6. 22:38·개발일지/CS

Why, Java?

  백엔드 개발에 참여하게 되면서 최근에 자바에 대해서 공부해보려고 하고 있습니다. Java + Spring을 사용하는 백엔드 팀이기 때문입니다. 최근에 kotlin으로도 Spring을 돌린다고 하던데, 일단 근본인 Java부터 명확하게 잡고 들어가고 싶습니다. 객체 지향이 만들어진 목적인 Java는 메모리를 어떻게 관리할까요?

 

  기본적으로 자바는 "한 번 작성하면 어디서든 실행된다. (Write Once, Run Anywhere)"라는 철학을 가지고 있습니다. 이게 가능한 이유는 소스 코드를 특정 기계에 종속된 언어가 아니라 "자바 바이트코드"로 컴파일한 후, 각 OS에 맞는 자바 가상 머신(Java Virtual Machine, JVM)이 이를 해석하여 실행하기 때문입니다. JVM으로 어떤 디바이스든 간에 동작할 수 있게됩니다.

 

Java의 Runtime Data Area

JVM이 프로그램을 수행하기 위해

 

1. OS로부터 할당받아 사용하는 메모리 영역인 Runtime Data Area

2. 이 메모리를 효율적으로 관리하기 위한 Garbage Collection

 

으로 핵심을 나눠볼 수 있습니다. WAS의 성능 문제가 발생했을 때, 대부분 이 영역들이 원인이 됩니다.

Rumtime Data Area

왼쪽 3개가 스레드 별로, 오른쪽은 공유된 자원.
더 상세한 Runtime Data Area figure.

 

  런타임 데이터 영역은 크게 스레드별로 생성되는 공간과 모든 스레드가 공유하는 공간으로 나뉩니다.

 

1. 스레드 별로 생성되는 영역 (Thread-Local)

  이 영역들은 스레드가 생성될 때 만들어지며 스레드가 종료되면 함께 소멸합니다. 자바에서 스레드는 

  • PC Register
    • 각 스레드마다 하나씩 존재하며, 현재 실행 중인 JVM Instruction의 주소를 저장합니다.
    • CPU의 레지스터와 달리 스택 기반으로 작동합니다.
  • JVM Stack
    • Thread의 Method가 호출될 때마다 Frame(프레임)이라는 블록이 생성되어 쌓이고, 메서드가 종료되면 삭제됩니다.
    • 각 프레임은 수행 정보, 즉 지역 변수 배열(Local Variable Array), 피연산자 스택(Operand Stack), 그리고 프레임 데이터(참조값 등)을 포함하게 됩니다.
      • 지역변수 배열: 매서드가 얼마나 많은 지역변수를 포함하고 있는지와 해당 값에 대한 정보가 저장됨.
      • 피연산자 스택: 중간 연산이 필요할 때, 연산 작업을 수행하기 위한 작업 공간의 역할.
      • 프레임 데이터: 메서드에 상응하는 모든 심볼이 이 곳에 저장됨.
    • 기본형 변수(Primitive type)와 객체 참조 변수(Reference)가 이곳에 저장되며 method 실행 중에만 데이터가 존재합니다. Method 호출이 종료될 때 stack에서 제거됩니다.
  • Native Method Stacks
    • Java가 아닌 다른 언어로 작성된 네이티브 코드를 실행하기 위한 영역입니다.
    • JNI(Java Native Interface)를 통해 호출되어 바이트코드로 전환하는 코드가 이곳에서 처리됩니다.
    • 일반 프로그램처럼 커널이 스택을 잡아 독자적으로 프로그램을 실행시키는 영역입니다.
    • 대표적으로 Thread.currentThread() 등이 있습니다.

2. 모든 스레드가 공유하는 영역 (Shared)

  이 영역들은 JVM이 시작될 때 생성되며, JVM이 종료될 때까지 유지됩니다.

  • Heap Area
    • new 키워드로 생성된 객체 인스턴스와 배열이 저장되는 공간.
    • 런타임에 동적으로 할당되며 가비지 컬렉터(GC)의 주요 관리 대상이 됨.
    • 모든 스레드가 공유하므로 동기화 문제가 발생할 수 있음.
  • Method Area
    • Method Area는 Interface이고, PermGen과 Metaspace는 그에 해당하는 Implementation이라고 할 수 있습니다.
    • 최초에는 PermGen(Permanat Generation Space)가 대신에 존재했습니다. Java 8 이후에는 이것이 Metaspace로 바뀌었습니다.
    • PermGen에서는 JVM Heap 메모리와 분리되어 내부에서 클래스 정보와 상수를 관리했습니다.
    • Metaspace는 PermGen에서 수정될 필요가 있는 데이터와 없는 데이터로 나눠서, 수정될 필요가 있는 데이터를 Heap으로 옮겨 최대한 GC의 대상이 되도록했고, 수정될 필요가 없는 데이터는 OS가 Native Memory에서 직접 관리하도록 위임했습니다.
    •  
    • Method Area에는 다음과 같은 특징들이 있습니다.
      • 클래스 로더에 의해 로딩된 클래스 파일의 바이트코드, 메타데이터, 정적 변수 (static) 등이 저장됨.
      • 런타임 상수 풀 (Runtime Constant Pool)이 속하며, 이곳에서 리터럴 상수와 메서드 및 필드에 대한 심볼릭 참조가 저장됨.
      • 이 영역에 등록된 class만이 Heap에 생성될 수 있음.

Stack vs Heap

Stack: 메모리 할당과 해제가 자동으로 이루어짐(컴파일러가 처리). -> 액세스 속도가 빠르고 스레드에 안전함.

Heap: 메모리 관리가 필요하며 (이를 GC가 담당.) 스택보다 엑세스 속도가 느리고 스레드 간에 데이터가 공유됨.

int myAge = 26; // 정수 리터럴
String name = "자바 메모리"; // 문자열 리터럴

 

상수(Symbolic Constant): final이나 const를 써서 변하지 않게 만든 변수(그릇)

리터럴(Literal): 그 변수에 들어가는 값 자체 (불변)

GC (Garbage Collector)

  Java는 기본적으로 프로그램 코드에서 메모리를 명시적으로 지정하여 해제하지 않습니다. 이 때문에 더이상 필요없는 객체를 찾아 지우는 작업이 필요한데, 이를 GC가 해결합니다. GC에는 아래의 두 가지 전제를 가정합니다.

Weak generational hypothesis

1. 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

 

  이 가설의 장점을 최대한 살리기 위해서 HotSpot VM(JVM 인터페이스의 구현체)은 물리 공간을 Young과 Old 영역의 두 부분으로 나눴습니다.

 

Heap에는 프로그램이 돌아가는 동안 계속해서 새로운 객체들이 만들어지면서 정신없이 채워지는 곳입니다. 많은 객체들이 존재하기 때문에 객체의 생명 주기에 따라서 크게 두 영역으로 나뉩니다.

 

마지막에 살아있나?의 아래 화살표는 YES가 되어야 합니다!

 

Heap의 구성

  • Young Generation
    • Eden: 객체가 최초로 생성되는 공간.
  1. Eden 영역이 꽉 차면 GC가 Eden 영역에 있는 요소의 참조 여부를 살펴서 GC를 진행함
  2. 이때 한번 GC가 되어 정리된 데이터들은 Survior Space로 이동함
  3. Minor GC가 끝난 데이터들은 먼저 S0 영역으로 이동하고 Eden 영역과 S1 영역이 clear됨
  4. 다시 Eden 영역을 채우고 Eden 영역이 다시 꽉참
  5. 이번엔 Eden 영역과 S0 영역에 대해서 Minor GC가 발생하고, 여기서 살아남은 데이터들은 S1 영역으로 복사되고 Eden과 S0가 clear됨.
  6. 즉, S0, S1 영역 중에서 하나의 영역에만 데이터가 존재하게 됨. 살아남은 데이터들을 옮겨놓으면서 계속 정리를 함.  이 과정이 minor GC임.
  • Old Generation
    • Promotion: S0과 S1을 반복적으로 이동하는 데이터는 age 값이 증가하는데, 특정 값 이상이 되면 Old Generation으로 이동되고 이 옮겨지는 것을 말함.
    • Promotion이 반복되면 Old 영역에 데이터가 쌓이게 되고, Eden 영역과 같은 방식으로 Old 영역 역시 GC를 통해 메모리에서 필요없어진 요소들을 제거함.
    • Old 영역에서 발생하는 GC를 Major GC라고 부름.
    • 그리고 Major GC와 Minor GC가 동시에 일어나는 것을 Full GC라고 함.
  • Permanent Generation
    • 생성된 객체들의 정보의 주소값이 저장된 공간.
    • 클래스 로더에 의해 load되는 class, method 등에 대한 meta 정보가 저장되는 영역이고 JVM에 의해 사용됨.

 

  위에서 볼수 있듯이 필요없는 객체들이 계속 쌓이면 창고가 꽉 차서 순식간에 아무것도 할수 없게 되어서 프로그램이 멈추게 됩니다.이를 JVM의 GC가 해결합니다.

 

GC Process

  기본적으로 GC는 Stop The World와 Mark & Sweep에 기초를 둔 방식으로 대상을 파악합니다.

  1. 먼저 GC를 하기 위해서 Stop The World를 진행 (Marking 작업을 위해서 모든 스레드를 중단.)
    1. 대개의 경우 GC를 튜닝하는 과정은 이 Stop The World 시간을 줄이는 것이라고 보면 됨.
  2. 모든 스레드를 멈추고, 스택 내의 모든 지역 변수를 스캔하면서 각각 어떤 오브젝트를 참조하고 있는지 찾고(Marking), 참조가 되어있지 않은 오브젝트들을 Heap에서 제거(Sweep)하게 됨. 즉, Mark & Sweep.

 

  GC는 목적에 따라서 골라 쓸수 있는 여러 도구가 담긴, 일종의 청소 도구함이라고 생각하면 좋습니다. 다음 예시가 있습니다.

  1. 아주 빠른 응답이 필요한 웹사이트 (Latency, 개별 요청에 대한 응답속도)
  2. 매우 큰 데이터를 한꺼번에 처리해야하는 분석 프로그램(Throughput, 전체적인 작업 효율)

  여기서 요구하는 성능이 명백하게 다르기 때문에 상황에 맞는 도구가 필요합니다. 이에 따라서 GC도 그 종류가 나뉘게 됩니다. 

 

  이에 따라서, 각 GC에 대한 방식에 대해서 작성해보겠습니다.

 

Serial GC

minor GC는 위의 기술된 내용의 방식을 그대로 사용하고, Old 영역의 GC는 mark-sweep-compact라는 알고리즘을 사용합니다.

  1. Old 영역에 살아 있는 객체를 식별(Mark)함.
  2. 그 다음, 힙의 앞 부분부터 확인하여 살아있는 것만 남김.(Sweep)
  3. 마지막으로 각 살아남은 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눔. (Compaction)

적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식입니다.

 

Parallel GC (Throughput GC)

  Serial과 기본적인 GC방법은 같습니다. 하지만 Parallel GC는 처리하는 스레드가 여러 개입니다. 그래서 Serial보다 더 빠르게 객체를 처리할 수 있습니다.

  Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리합니다.

 

CMS GC (Low Latency GC)

  Serial GC에서 Initial Mark와 Concurrent Mark, Remark, Concurrent Sweep이 추가된 방식입니다.

Serial vs CMS

 

  GC가 동작을 시작하면 똑같이 Stop The World/Pause를 진행합니다. 여기서 Initial Mark를 진행하는데, 클래스 로더에서 가장 가까운 객체 중 살아있는 객체만 찾는 것으로 Mark 단계를 끝냅니다. 이 덕분에 멈추는 시간이 매우 짧아집니다.

  Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인합니다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것입니다. 

  Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인합니다. 마지막으로 Concurrent Sweep 단계에서 쓰레기를 정리하는 작업을 실행합니다. 이 작업 또한 다른 스레드가 실행되고 있는 상황에서 진행합니다.

 

  이러한 과정으로 인해 Stop The World 시간이 매우 짧습니다. 응답 속도가 매우 중요한 애플리케이션인 경우 CMS GC를 사용합니다. 다만 이러한 방식으로 인해서 다른 GC보다 메모리와 CPU를 더 많이 사용하고, Compaction 단계가 기본적으로 없습니다. 조각난 메모리가 더 많아서 Compaction을 따로 수행해도 작업이 평균보다 더 길어져서 Stop The World 시간이 더 길어집니다.

 

G1 GC

  Yong의 세 가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식입니다. CMS GC를 대체하기 위한 목적으로 만들어졌습니다.

  G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행하다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행합니다. 이 바둑판은 하나 하나의 블록(Region)으로 쪼개져있습니다. 이 블록들은 고정된 역할이 없고, 상황에 따라서 어떤 블록은 Eden이 되고, 어떤 블록은 Old가 됩니다.

  동작의 과정은 다음과 같습니다.

 

Young Generation

  1. 먼저 새로운 객체가 생성되어 Eden 역할을 맡은 Region들이 꽉 찹니다.
  2. G1이 이 Eden Region들만 골라서 청소합니다.
  3. 살아남은 객체들을 Survivor Region이나 Old Region으로 이사시킵니다.
  4. Stop The World는 청소할 구역에서만 진행하므로 매우 짧아집니다.

Old Generation

  1. CMS와 비슷하게 Concurrent Marking을 진행합니다. 애플리케이션이 돌아가는 동안 계속해서
    "어떤 Region에 쓰레기가 제일 많은지" 분석합니다.
  2. minor GC를 할 때가 됐는데 Old 영역에도 일정량의 쓰레기가 찼다고 판단되면 Mixed GC를 사용합니다.
  3. Mixed GC는 Young Region 전체 + Old Region 중 쓰레기가 많은 일부의 Region을 청소합니다.

Homongous Region

  1. 일반적인 Region 크기의 50%를 넘는 거대한 객체가 들어올 경우, 이 영역으로 배치해서 일반 객체와 섞이지 않게 해 관리 효율을 높입니다.

  과정에서 볼 수 있듯이, Old 영역 전체를 뒤지는 게 아니라 쓰레기가 많이 쌓인 Old 영역을 계속 트래킹해서 해당 Region, 즉 "청소 효율이 좋은 영역"(Garbage First, G1)만 골라서 처리하므로 위의 3개의 GC보다 훨씬 빠르다는 특징이 있습니다. 

 

  이 GC의 가장 큰 특징은 유연하면서 효율하다는 점입니다.

  1. 개발자가 Stop The World Limit을 설정할 수 있습니다. G1은 설정된 그 시간 안에 처리할 수 있는 만큼의 Region만 골라서 청소하게 됩니다.
  2. 힙 메모리가 아무리 커져도 결국은 쓰레기가 많은 곳만 골라서 청소하기 때문에 성능 저하가 적습니다.
  3. Region 단위로 객체를 복사해서 옮기기 때문에 Compaction 효과가 자동으로 일어나 단편화 문제가 거의 없습니다.

Evacuation 발생 시 내부 G1 GC의 동작 방식

  이러한 GC의 등장으로 힙 메모리가 아무리 커도 Stop The World 시간이 10ms를 넘지 않게 됐습니다. 이에 Java는 초저지연으로 메모리를 관리할 수 있게 됐습니다.

 

Generational ZGC

  Stop The World 시간을 1ms을 넘지 않도록 하기 위해서 ZGC가 Java 21에 도입됐습니다. 초기 ZGC에는 세대 구분이 없었으나 Generational ZGC에서 이에 대한 구분이 추가되었습니다.

 

  ZGC의 핵심은 Colored Pointers와 Load Barrier에 있습니다.

 

Colored Pointers

 보통의 GC는 객체가 살아있는 지, 죽어있는 지를 판별할 때 객체의 헤더(메모리 자체)에 기록합니다. 하지만 ZGC는 64비트 주소 중에서 실제 메모리 주소를 나타내는 비트를 제외한 나머지 비트를 Color로 사용하여 상태를 표시합니다. 즉, 객체를 가리키는 주소값 자체에서 살았는지 죽었는지를 표시합니다. 이에 메모리 참조를 하지 않고도 살아있는 지 죽어있는 지 여부를 알아낼 수 있습니다.

 

Load Barrier

 애플리케이션이 객체의 필드를 읽으려고 할 때마다(Load) JVM이 중간에 끼어들어서 (Barrier) 검사를 합니다. 이후에 ZGC가 포인터의 Color를 확인하고 비정상 Color이면 객체의 주소를 변경하고 올바른 값을 리턴(Self-Healing)해줍니다.

 

이 과정으로 인해 대용량의 메모리를 쓰면서도 Stop The World 시간은 1ms 미만이 될 수 있었습니다.

 

Generational ZGC의 특징

 

 

결론

  이렇게 자바의 메모리 관리 전략을 알아보았습니다. 공부를 하다보니, JVM에 대해서 점점 알게되면 서비스의 규모가 커지면 커질 수록 성능 최적화에서 빛을 발할 것 같습니다. 아직 많은 공부가 필요하다고 느껴지기도 했네요. Java와 좀 더 친해진 것 같습니다!

 

참고 문헌

https://d2.naver.com/helloworld/1329

https://jithub.tistory.com/296

https://jithub.tistory.com/40

https://velog.io/@wkdwoo/JVM-Runtime-Data-Area

 

Java Heap (with GC)

■Heap이란 - 인스턴스와 배열이 동적으로 생성되는 공간. - 생성에 필요한 인스턴스와 배열의 메타 정보는 Heap 내의 Method Area에서 얻어온다. - 모든 Thread가 공유하기 때문에 동기화 문제가 발생할

jithub.tistory.com

Gemini의 이미지 생성 모델 사용

 

반응형

'개발일지 > CS' 카테고리의 다른 글

[Network] 네트워크 대역폭의 측정과 OS와의 관계  (0) 2026.03.03
[Util] Jackson의 특징과 POJO의 직/역직렬화 과정  (0) 2026.02.13
[Thread] Message Broker와 스레드 (feat. Kafka)  (0) 2026.01.19
[Thread] 자바의 스레드 관리 전략  (0) 2026.01.12
[Thread] User thread vs Kernel thread  (0) 2026.01.12
'개발일지/CS' 카테고리의 다른 글
  • [Util] Jackson의 특징과 POJO의 직/역직렬화 과정
  • [Thread] Message Broker와 스레드 (feat. Kafka)
  • [Thread] 자바의 스레드 관리 전략
  • [Thread] User thread vs Kernel thread
soberyl
soberyl
개발과 청춘 일지
    반응형
  • soberyl
    soberyl 님의 블로그
    soberyl
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 프로젝트
        • Snap Trade
        • 팀 프로젝트
        • 오픈 소스
      • 개발일지
        • 코딩 테스트
        • 알고리즘
        • CS
      • Full stack
      • Frontend
        • UXUI
        • React
        • Next.js
        • React Native
        • Monitor
      • Backend
        • NestJS
        • Spring
        • Monitor
        • DB
      • DevOps
        • CICD
      • Test
      • AI
      • Infra
        • AWS
        • On-premise
      • 청춘
        • 희곡
        • 연극
      • 잡설
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    io
    java
    테스트
    DB
    알고리즘
    희곡
    연극
    thread
    프론트엔드
    MySQL
    CPU
    코테
    백엔드
    EC2
    코딩테스트
    최적화
    설계
    거래소
    memory
    OS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
soberyl
[Memory] 자바의 메모리 관리 전략
상단으로

티스토리툴바