Garbage Collection

김동헌·2024년 2월 20일
3

Java

목록 보기
1/4
post-thumbnail

Garbage Collection


배경 및 문제

과거의 언어들은 동적 메모리 할당 기능이 없었거나, FORTRAN이나 C와 같은 언어에서는 개발자가 메모리를 할당하고 해제하는 작업을 직접 수행했었습니다.

그러나 이러한 수동 관리는 인간의 실수로 인해 메모리 누수가 발생하거나, 메모리를 잘못 사용하거나, 중복으로 해제하는 등의 버그를 유발했습니다.

이러한 버그들은 프로그램의 예상치 못한 동작이나 비정상 종료를 초래할 뿐만 아니라, 보안 취약점으로 이어질 수도 있습니다.

실제로, Microsoft에 따르면 윈도우 보안 취약점의 약 70%가 메모리와 관련된 문제로 발생한다고 합니다.


해결 방안

이러한 문제를 해결하기 위해서 제시된 것이 가비지 컬렉션입니다.

가비지 컬렉션은 프로그래머가 직접적으로 메모리를 할당하고 해제하는 것 대신, 프로그램 실행 중에 더 이상 필요하지 않은 메모리를 자동으로 식별하고 해제합니다.

Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거

이를 통해
프로그래머는 메모리 관리에 대한 부담을 줄이고,
런타임 시스템이 자동으로 메모리를 관리
를 하도록 할 수 있습니다.

따라서 가비지 컬렉션은 프로그램이 실행되는 동안 발생하는 쓸모 없어진 메모리를 효율적으로 관리하여 프로그램의 성능과 안정성을 향상시키는 데 도움을 줍니다.

Java에서는 가비지 컬렉터를 이용해 Java 프로세스한정된 메모리를 효율적으로 사용할 수 있고, 개발자 입장에서는 메모리 관리, 메모리 누수(Memory Leak) 문제에서 대해 관리하지 않아도 되서 개발에만 집중할 수 있습니다.

또한 Java 뿐만 아니라 여러 프로그래밍 언어에서 사용되는 일반적인 개념입니다.
예를 들어, Python, JavaScript, Go와 같은 다양한 언어에서도 가비지 컬렉션 기능이 내장되어 있습니다.


코드로 보자!

for (int i = 0; i < 9999; i++) {
  NewObject obj = new NewObject();  
  obj.something();
}

위 코드를 살펴보면,
반복문 내에서 생성된 객체들은 루프가 종료된 이후에는 더 이상 사용되지 않지만 메모리를 계속 점유하게 됩니다.
이러한 상황에서, 메모리 자원은 점차적으로 고갈될 수 있습니다.

그러나, Java에서는 가비지 컬렉션(GC)이라는 메커니즘이 존재하여 이러한 일회성 객체들을 주기적으로 정리함으로써, 한정된 메모리 자원을 효율적으로 관리할 수 있습니다."

단점

가비지 컬렉션에도 단점이 존재합니다.

가비지 컬렉션은 메모리를 자동으로 관리해주지만, 메모리가 언제 정확히 해제될지 예측하기 어렵습니다. 또한, 가비지 컬렉션 동작 중에는 프로그램이 일시적으로 멈출 수 있어 성능에 영향을 줄 수 있습니다.

특히 대규모 프로그램이나 실시간 요구에 민감한 응용 프로그램에서는 이러한 중단이 문제가 될 수 있으므로, 가비지 컬렉션을 사용할 때는 이러한 제약사항을 고려하여 프로그램을 설계해야 합니다.

이러한 현상을 Stop-The-World라고 합니다.


STW(Stop-The-World)

GC메모리를 회수하기 위해 작업을 수행할 때,
정확한 메모리 회수를 위해 Java 가상 기계(JVM)가 관리하는 모든 애플리케이션 스레드를 일시 정지시키는 현상을 Stop-The-World (STW)라고 합니다.

이 현상은 GC가 실행되는 동안 프로그램의 실행이 멈추게 되므로, 애플리케이션의 응답성이 저하됩니다. 따라서, STW의 시간을 가능한 한 최소화하는 것이 중요합니다.

가비지 컬렉션이 너무 자주 실행될 경우, 소프트웨어의 성능이 저하될 수 있습니다. 예를 들어, 과거 인터넷 익스플로러가비지 컬렉션을 너무 자주 실행하여 성능 문제를 일으키는 것으로 악명이 높았습니다.

실시간 성능이 중요한 프로그램의 경우, 가비지 컬렉션에 의존하는 것이 적합하지 않을 수 있습니다. 이에 따라, 애플리케이션의 사용성을 유지하면서 메모리 관리를 효율적으로 수행할 수 있도록 가비지 컬렉션을 최적화하는 작업, 즉 GC 튜닝이 개발자에게 중요한 과제가 됩니다.

GC 튜닝은 가비지 컬렉션의 효율을 높이고 STW의 지속 시간을 최소화하여, 애플리케이션의 응답성과 성능을 개선하기 위해 사용되며,
해당 포스팅에서는 GC 튜닝을 다루지는 않습니다 : )


GC Target

GC는 특정 객체가 garbage인지 아닌지 판단하기 위한 도달능력(Reachability)

도달 가능성 기본 개념

  • Reachable(도달 가능)

    • 객체가 직접적으로 또는 다른 참조된 객체들을 통해 애플리케이션 코드에서 접근할 수 있는 상태
    • 이러한 객체는 사용 중이거나 사용될 가능성이 있으므로 GC에 의해 회수되지 않음
  • Unreachable(도달 불가능)

    • 객체에 대한 유효한 참조가 더이상 존재하지 않는 상태 즉, 애플리케이션 코드에서 더이상 그 객체에 접근할 방법이 없음을 의미
    • 도달 불가능한 객체는 더 이상 사용되지 않으므로 GC의 대상이 되어 메모리에서 회수될 수 있음

도달 가능성의 판단 기준은 GC가 메모리 관리를 효율적으로 수행하는 데 핵심적인 역할을 합니다.

객체의 도달 가능 상태도달 불가능 상태구분함으로써,
GC는 메모리 내의 해당 객체를 안전하게 회수할 수 있습니다.

이 과정에서,
객체에 유효한 레퍼런스가 존재하면 해당 객체는 Reachable로 간주되어 회수되지 않습니다.
반면, 유효한 레퍼런스가 없는 객체는 Unreachable로 구분되어 GC의 대상이 됩니다.

이를 통해 JVM은 메모리를 효율적으로 관리하고 메모리 누수를 방지하여 애플리케이션의 성능을 유지할 수 있습니다.


GC의 청소 방법

도달 가능성에 따라 GCUnreachable한 객체를 어떻게(?) 청소하는지 알아보겠습니다.


Mark & Sweep

Mark & Sweep은 다양한 가비지 컬렉션(GC)에서 사용되는 기본적인 객체 회수 알고리즘입니다.

Mark and Sweep 방식에서는 객체가 루트로부터 접근 가능한지 여부를 기준으로 메모리 해제를 결정합니다.

JVMRoot Space
메소드 영역, 정적 변수, 스택, 네이티브 메소드 스택
참조하는 힙 메모리를 포함합니다.

이 방법은 세 단계로 구성됩니다.

  1. 첫 번째 단계Mark 단계에서는, GC도달 가능한 모든 객체를 탐색하며 표시합니다.
    이 과정에서 애플리케이션의 루트 집합에서 출발하여 참조를 따라가면서 도달할 수 있는 모든 객체에 표시를 남깁니다.

  2. 두 번째로, Sweep단계에서는 표시되지 않은, 즉 도달 불가능한 객체들을 메모리에서 제거합니다.

  3. 마지막 Compact 과정은 Sweep 단계 후에 발생하며,
    메모리 내의 분산된 객체들을 힙의 시작 주소로 모아서 메모리의 사용된 부분과 사용되지 않은 부분을 명확하게 분리합니다.
    이 압축 과정을 통해, 메모리 할당 및 접근 속도를 향상시키고, 메모리의 효율적인 사용을 가능하게 합니다.
    (GC 종류에 따라 하지 않는 경우도 있음)


Mark 단계

Mark 과정에서는 Root Space에서 시작하여 그래프 순회를 통해 연결된 모든 객체를 탐색하고, 찾아낸 객체들을 마킹합니다.

이 객체들은 그림상에서 초록색 원으로 표시됩니다.

마킹 전


마킹 후


Sweep 단계

Sweep 과정에서는 Heap 내의 Unreachable 객체들, 즉 참조되지 않는 객체들을 제거합니다.

이 객체들은 그림상에서 파란색 원으로 표시됩니다.


삭제된 모습


Compact 단계

Compact 과정에서는 Sweep 후, 분산된 객체들을 Heap의 시작 주소로 모아 메모리를 압축합니다.


Heap 메모리

JVMHeap영역은 동적으로 생성된 객체들이 저장되는 곳으로, GC의 주요 대상이 되는 메모리 공간입니다.

이 영역의 설계는 약한 세대 가설(Weak Generational Hypothesis)을 기반으로 하며, 다음 두 가지 주요 가정에 기초하게 됩니다.

  1. 대부분의 객체는 생성 후 짧은 시간 내에 접근 불가능한 상태(Unreachable)가 됩니다.
    즉, 객체들이 빠르게 생성되고 소멸되며, 대부분 일회성으로 사용됩니다.
  2. 오래된 객체에서 새로운 객체로의 참조는 매우 드뭅니다.
    즉, 시간이 지남에 따라 객체 간의 참조 관계는 대부분 크게 변하지 않으며,
    새 객체들은 오래된 객체들에 의해 참조되는 경우가 적습니다.

이러한 두 가지 주요 가정을 바탕으로,
JVM 개발자들은 메모리 관리를 보다 효율적으로 하기 위해 Heap 영역을 물리적으로 두 부분을 나누었습니다.

Young 영역(Minor GC) : 새로 생성된 객체들이 배치되는 곳으로,
이 객체들 중 대부분은 빠르게 Unreachable 상태가 되어 GC에 의해 회수됩니다.

Old 영역(Major GC || Full GC) : Old 영역은 시간이 지나며 여전히 사용되는
즉, 오래된 객체들이 저장되는 곳으로,여기서의 객체들은 비교적 덜 자주 회수됩니다.

초기 JVM 설계에서는 Perm(영구) 영역도 존재했으나,
이는 주로 JVM의 메타데이터와 클래스 구조 같은 정적 데이터저장하는 곳이었습니다.

Java 8부터는 이 Perm 영역이 제거되고, 그 기능은 Metaspace로 대체되었습니다.

Heap 메모리 영역을 세대별로 나누는 설계는 객체의 생존 기간을 고려한 메모리 관리 전략의 방법으로, GC의 효율성을 크게 향상시킵니다.


Young의 3가지 영역

효율적인 GC를 위해 Young(Minor GC)
3가지 영역(Eden, Survivor 0, Survivor 1)으로 나눕니다.

  1. Eden 영역은 새로 생성된 객체들이 배치되는 곳입니다.
    여기서 객체들은 처음으로 가비지 컬렉션(GC)을 겪게 되며,GC 후에 살아남은 객체들은 Survivor 영역으로 이동합니다.

  2. Survivor 영역은 두 부분, Survivor 0Survivor 1으로 나뉘며, 최소 한 번의 GC를 거쳐 살아남은 객체들이 저장되는 곳입니다.
    두 영역 중 하나는 항상 비어 있어야 하며,
    GC살아남은 객체들은 비어 있는 Survivor 영역으로 이동합니다.

힙 영역을 세분화함으로써,
객체의 생존 기간을 정밀하게 관리하여 GC불필요한 객체를 효과적으로 제거합니다.


Java 8의 Permanent

Permanent 영역클래스와 메소드의 메타데이터를 저장하는 곳으로, Java 7까지 힙에 포함되었습니다.

Java 8부터는 이 영역이 Metaspace로 대체되어, 네이티브 메소드 스택으로 이동했습니다.


Minor GC 과정

  1. 처음 생성된 객체는 Eden영역에 위치
  2. 객체가 계속 생성되어 Eden영역이 Full하면 Minor GC가 실행
  3. Mark를 통해 Reachable 객체를 탐색살아남은 객체Survivor영역으로 이동
    이 객체들은 그림상에서 파란색으로 표시됩니다.
  4. Eden영역에서 사용되지 않는 객체(Unreachable)메모리를 해제(Sweep)
  5. 살아남은 모든 객체는 age 값이 1 증가

Survivor 영역에서, 객체의 age 값은 해당 객체가 GC를 통해 살아남은 횟수를 나타냅니다.

이 값은 객체의 헤더에 기록되며, 일정 횟수(기본적으로 31회)를 넘으면 객체는 Old 영역으로 이동합니다.

이는 HotSpot JVM에서 객체 헤더의 6비트를 사용하여 age 값을 기록하기 때문에 가능합니다.

또한, Survivor 영역의 운영 규칙에 따라, 두 Survivor 영역 중 하나는 반드시 비어 있어야 합니다.


Major GC 과정

Old Generation장기간 사용되는 객체들이 모이는 메모리 영역입니다.

이곳의 객체들은 원래 Young Generation에서 시작되었지만, 여러 번의 GC를 거치며 age 임계값에 도달하여 Old 영역으로 이동한 객체들입니다.

Old 영역에서 메모리가 부족해지면, Major GC발생하여 이 영역의 객체들을 청소합니다.


Minor GC와 Major GC 차이

Major GCOld Generation 영역의 메모리가 꽉 차면 실행되며, 이 과정에서 참조되지 않는 모든 객체들을 검사하여 일괄적으로 제거합니다.

Old Generation큰 메모리 공간을 차지하기 때문에 GC가 상대적으로 오래 걸립니다.

예를 들어,
Young GenerationMinor GC짧은 시간 내에 완료되지만,
Old GenerationMajor GC는 훨씬 더 긴 시간이 소요되며 시스템에 부담을 줄 수 있습니다.

이 때 발생하는 Stop-The-World 현상애플리케이션의 작업을 중단시키고 성능 저하를 일으킬 수 있어, 자바 개발자들은 지속적으로 GC 알고리즘을 개선해 왔습니다.

GC 알고리즘은 다음 포스팅에 이어서 진행하겠습니다.

Minor, Major Gc 차이를 표로 보기

GC 종류Minor GCMajor GC
대상Young GernerationOld Gerneration
실행 시점Eden 영역이 꽉 찬 경우Old 영역이 꽉 찬 경우
실행 속도빠름느림
profile
백엔드 기록 공간😁

4개의 댓글

comment-user-thumbnail
2024년 2월 20일

잘 읽고 갑니다~

1개의 답글
comment-user-thumbnail
2024년 2월 21일



1개의 답글