[Java] 가비지컬렉션의 개념과 동작 과정

김유정·2022년 10월 10일
0


혹시 인사이드에서 이 장면 아는 분 있으신가요? 많은 분들을 울렸던 장면이죠..
한때는 라일리가 가장 좋아했던 상상속 친구 "빙봉"이죠.
빙봉은 라일리가 커갈수록 잊혀지게 되고, 결국 기억의 매립지로 사라지게 되죠.

영화에서 기억을 정리하는 것처럼 Java에서는 JVM이 메모리를 관리하고 더 이상 사용되지 않는 객체들을 삭제하는 역할을 합니다.

부끄럽지만, 지금까지 Java로 개발을 하면서 메모리가 어떻게 관리되는지 잘 알 지 못했습니다. 그럼에도 아무 문제 없이 개발했던 건 C나 C++과는 다르게, 메모리를 관리해주는 JVM이 있었기 때문입니다. 이제는 알아두면 좋을 주제같아서 공부를 하게 되었습니다.

가비지 컬렉션(GG, Garbage Collection)이란?


JVM의 동적으로 할당되는 메모리 영역 Heap 에서 필요 없게 된 메모리를 주기적으로 삭제하는 프로세스를 말합니다.

장점

  • 메모리 관리나 누수 문제를 개발자가 관리하지 않아도 됩니다.
    (의도적으로 System.gc()를 사용하여 호출할 수 있지만, 오버헤드가 매우 크므로 좋지 않기 때문에, 믿고 쓰는 게 좋다고 합니다.)

단점

  • GC를 수행할 땐 다른 동작을 멈추기 때문에 오버헤드가 발생하는데, 수행되는 시점을 파악하기 어렵습니다.
    (Heap의 특정 메모리 공간이 다 찼을 때 수행되기 하지만, 그 시점을 정확하게 예측하기는 힘들다고 합니다.)

Stop-The-World


  • GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것을 말합니다.
  • GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춥니다.
  • 어떤 GC 알고리즘을 사용하더라도 Stop-The-World는 발생하기 때문에, 성능향상을 위해 고려해야할 건 Stop-The-World를 발생시키지 않도록 하는 것이 아니라 시간을 최소화 시키는 것입니다.

GC 알고리즘
1. Serial GC
2. Parallel GC
3. Parallel Old GC
4. CMS(Concurrent Mark Sweep) GC
5. G1(Garbage First) GC

GC 단계

세부적인 동작 방식은 다르지만, GC를 수행할 때 기본적으로 2가지 단계를 따른다고 합니다.

  1. Stop-The-World
  2. Mark and Sweep

Stop-The-World 단계에서 다른 쓰레드들을 모두 중지시키면, Mark and Sweep 과정이 일어나는 것입니다.

Mark

Root 객체에서부터 시작하여 참조되고 있는 객체와 그렇지 않은 객체를 구분합니다.

여기서 Root 객체가 될 수 있는 것은 다음과 같습니다.

  1. 실행중인 쓰레드 (Active Thread)
  2. 정적 변수 (Static Variable)
  3. 로컬 변수 (Local Variable)

참조되고 있는 것과 아닌 것에 대해 조금 더 자세히 살펴보겠습니다.

위와 같이 객체들을 실질적으로 Heap 영역에서 생성되고 Method Areask Stack Area 등 Root Area에서는 생성된 객체의 주소만 참조하는데요.

메서드 종료와 같은 이벤트 후에 객체를 참조하고 있는 변수가 사라진다면, 해당 객체는 참조되고 있지 않은 상태이므로 더 이상 사용할 일이 없습니다. 따라서 GC의 대상이 됩니다. 이러한 객체를 "Unreachable"하다고 표현하기도 합니다.

Reachable: 객체가 참조되고 있는 상태
Unreachable: 객체가 참조되고 있지 않은 상태

Sweep (= Delete)

주로 Sweep이라고 하는데, Delete라고 표현하는 영상이나 글도 있어서 헷갈렸습니다.

제가 이해하기에는 "메모리를 휩쓴다."는 의미에서 Sweep을 사용하기도 하고, "참조되지 않는 객체를 삭제한다"는 의미에서 Delete라고도 하는 것 같습니다. 주로 Mark and Sweep이라고 붙여서 많이 사용되는 것 같습니다.

참조되고 있지 않다고 Mark한 객체를 삭제하는 과정입니다.

Compact

말 그대로 압축단계인데, 남아있는 객체들을 쭉 모아서 압축시킵니다. 그렇게 되면, 새로운 객체가 생성되어 메모리를 할당해야할 때, 조금 더 쉽고 빠르게 진행이 가능하다고 합니다. 성능향상을 위해 수행된다고 하는데, JVM 종류에 따라서 실행여부가 다른 것 같습니다. 하지 않는 경우도 있다고 하네요.

Minor GC vs Major GC

  • Minor GC: Young Generation 안에서 일어나는 GC
  • Major GC: Old Generation(= Tenured Generation)안에서 일어나는 GC

자세한 건 아래 GC 동작 과정에서 살펴보시면 좋을 것 같습니다.

GC 동작 과정 예시


동작을 조금 더 쉽게 이해하고자 애니메이션을 만들어봤습니다.

1단계: Filling

새로운 객체가 생성되면, Eden 영역에 할당되게 됩니다. Eden 영역이 다 채워지게 되면, Minor GC가 일어납니다.

2단계: 첫 번째 Minor GC

Marking → Deleting → Copying 이렇게 순서대로 GC가 수행됩니다.
Deleting 후 살아남은 객체는 Survivor 중 한 공간으로 이동합니다.

3단계: Filling + 두 번째 Minor GC

새로운 객체들로 인해 Eden 공간이 채워지게 되면, 또 다시 Minor GC가 일어나는데요. 이 때 주의깊게 볼 것은 Copying 과정입니다. 살아남은 객체를 옮길 때에는 두 개의 Survivor 공간 중 이미 채워져있는 공간으로 이동시킵니다.

4단계: 세 번째 Minor GC

위 에니메이션에서 보면 Copying을 다 끝내기 전에 Survivor0 공간이 가득차게 됩니다. 그래서 세 번째 GC가 수행됩니다.

Survivor0에서 살아남은 객체들은 Survivor1으로 이동하게 됩니다. 아까 이동하지 못했던 Eden의 객체들도 이동합니다.

눈썰미 좋은 분들은 눈치채셨을 것 같은데요. 객체에 쓰여져있는 나이 에 주목해주세요. GC 과정에서 살아남을 때마다 1씩 증가합니다.

5단계: 계속된 Minor GC (4번째, 5번째 Minor GC)

계속해서 Filling → Marking → Deleting → Copying 과정이 반복적으로 수행됩니다.

그럼 이쯤에서 Old Generation 은 언제 사용되는 건지 Major GC 는 언제 일어나는건지 궁금하실 것 같아요.

6번째 Minor GC를 보면서 설명드리겠습니다.

6단계: 여섯 번째 Minor GC

위에서 잠시 언급한 것처럼 GC에 살아남을 때마다 객체의 나이가 1씩 증가하는데요. 특정 나이를 넘게 되면, Old Generation으로 이동합니다.
(이 예시에서는 설명을 위해 기준을 좀 낮게 3으로 설정했습니다.)

7단계: 첫 번째 Major GC

지금까지 Minor GC에 대한 수행과정을 살펴봤는데요. Minor GC에서 살아남은 객체들이 Old Generation으로 이동하게 되면, 언젠가 Old Generation도 꽉 차게 되겠죠. 그 때 Major GC가 일어납니다.

이렇게 예시의 모든 단계를 살펴봤는데요. GC를 이해하는데 도움이 되었으면 좋겠네요.

Permenent Generation


  • GC 동작 과정 예시에서는 살펴보지 않았던 Permenet Generation은 JVM에서 사용하는 메모리 공간이라고 합니다.
  • 클래스와 메서드를 사용하기 위해 JVM으로 요구되는 메타데이터가 포함되어 있습니다.
  • 응용 프로그램에서 사용하는 클래스를 기반으로 런타임에 JVM에 의해 채워집니다.
  • Java SE 라이브러리 클래스 및 메서드가 여기에 저장될 수 있습니다

이 부분에 대한 정보는 많지 않아서 이 정도로 찾아봤습니다. 여기서도 GC가 일어나는지는 잘 모르겠어서 궁금하네요.

여기까지 읽어주신 분이 있다면 감사합니다😃 잘못된 정보를 댓글로 피드백해주시면 빠르게 수정하도록 하겠습니다.

참고


0개의 댓글