Article/OS - Deep Dive

메모리 압축(Memory Compaction), 그리고 Garbage Collector

조금씩 차근차근 2025. 4. 15. 02:23

우리는 단편화 문제에서 메모리 압축이 필요한 이유를 알 수 있었다. 지금부터 메모리 압축에 대해서 알아보자.

메모리 압축 방식

가장 기본적이고 간단한 메모리 압축은 두가지 방식으로 나뉘어진다.

Sliding

  • 살아있는 객체들을 전부 한쪽 끝으로 몰아넣는다.

장점

  • 추가 메모리의 요구가 없다.

단점

  • 전체 메모리가 크게 이동해야 하기 때문에, stop-the-world 시간이 길어진다.

Copying Compaction

  • 동작 방식
    • 공간을 절반으로 나눈다.
    • 1회 압축시마다, 데이터를 반대쪽으로 이동시킨다.
      • 이를 압축마다 반복한다.

장점

  • 구현이 단순하다.
  • Bump Allocator 연산을 사용할 수 있기 때문에, Stop-the-world 시간이 빠르다.
    • sizeof(object) 연산을 이용해 다음 빈 포인터를 O(1)만에 찾는다.
      • 메모리의 이동 패턴이 단순해진다.
    • 단점은 메모리 해제를 직접 할 수 없다는건데, 이건 GC가 해주면 된다.

단점

  • 사용할 수 있는 실제 공간이 줄어든다.
  • 오래 살아남는 객체는 의미없이 복사되는 과정이 반복된다.

Stop-the-world는 왜 일어나는가?

손상된 힙

  • GC가 객체의 참조관계를 파악해야 하는데,
    • 애플리케이션 스레드가 계속해서 메모리를 할당/해제 하면
    • 정확한 참조 관계를 파악할 수 없게 된다.

댕글링 포인터

  • 기존에 참조하던 곳에 있던 데이터가 사라졌다!
  • 사용하고 있던 도중에, compaction 한다고 내 메모리의 위치가 변경되면?
    • 포인터가 갑자기 허공을 바라보게 된다.
    • 이래서 필요한게, stop the world!

Stop-the-world의 문제점

  • 모든 스레드가 멈춘다.
    • 이는 실시간성이 중요한 서비스에는 큰 단점으로 다가온다.

해당 stop-the-world 시간을 줄이기 위해

  • 나눠서 GC
    • 메모리 컴팩션을 수행하는 영역을 최소화하고
    • 메모리 컴팩션을 한번에 수행하는 것이 아닌, 여러 영역에 걸쳐 나눠서 수행하는 것이다.
  • 세대 개념 도입
    • Young Generation → Copying Compaction
    • Old Generation → Sliding Compaction
    • 어느정도 오래 살면, 굉장히 오래살더라~ 라는 이론을 도입한다.

GC는 왜 쓰는가?

  • 개발자의 메모리 관리 생산성 향상
    • 이때, 메모리를 지운 상태로만 그대로 두면, 메모리 fragmentation이 매우 심해진다.
  • 그래서 우린, GC 시 메모리 컴팩션도 함께 수행하는 것이다.

간단한 Generational GC 동작 방식

  • Eden 영역
    • 신규로 할당할 힙 메모리는 Eden 영역을 사용한다.
      • 해당 Eden영역은 스레드별로 별도로 할당되어 있어, 동시성 문제에 대하여 자유롭다.
        • (동시에 여러 스레드가 같은 메모리주소에 할당하려고 하지 않는다.)
    • eden 영역에서 한번 살아남은 객체는 Young Generation으로 이동한다.
    • Copying Compaction 을 사용한다.
  • Young Generation
    • Copying Compaction 을 사용한다.
  • Old Generation
    • Sliding Compaction 을 사용한다.