Garbage Collection이란?

C와 Java의 차이점 (메모리 관점)

C는 포인터를 통해 컴퓨터 메모리를 직접 참조할 수 있다. 하지만 Java는 JVM이 컴퓨터의 메모리의 공간을 할당받아 프로그램을 동작하며 컴퓨터의 메모리주소를 직접 참조할 수 없다. C언어는 동적으로 메모리를 할당한 후 사용이 끝나면 반드시 해제해주어야 한다. 반면 Java는 개발자가 직접 해제해줄 필요 없이 JVM 내부에서 Garbage Collection(이하 GC)이란 과정을 통해 메모리를 해제한다.

Garbage Collection이란?

JVM의 메모리 관리 방식으로 Heap영역에 할당받은 메모리 공간을 주기적으로 정리하는 과정을 말한다. 개발자가 직접 Heap 영역에 할당한 메모리를 해제해주지 않아도 JVM 내부에서 주기적으로 정리한다. 물론 개발자가 GC를 직접 실행할 수 있지만 권장하지 않는다. 또한 개발자가 GC의 옵션을 수정하여 프로그램의 성능향상을 기대할 수 있다.

GC의 단점

Stop-The-World

줄여서 STW라고 한다. GC를 실행할 때 GC를 실행중인 쓰레드를 제외한 모든 쓰레드는 동작을 멈춘다. 물론 GC의 전체 과정에서 항상 STW가 발생하는 것은 아니고 특정 단계에서 발생한다. STW 시간이 길어질 수록 프로그램의 성능은 낮아진다. 즉 너무 자주 GC를 실행시키면 프로그램이 느려질 수 밖에 없다. 따라서 Garbage Collector는 STW를 줄이는 방향으로 발전하고 있고, 개발자는 GC가 덜 발생하는 방향으로 개발을 진행해야한다.

GC 시점을 예측하기 어려움

개발자가 GC를 직접 실행하는 것은 매우 위험하다. JVM 내부에서 때가 되면 알아서 GC를 실행할 것이다. 미사일을 발사하기 직전에 GC가 실행되어 STW가 발생한다면 정말 위험할 것이다. 아주 섬세한 실시간 시스템을 사용하는 곳에서 Java를 사용할 때는 주의해야한다.

GC 알고리즘

GC를 수행하는 Garbage Collector는 여러 종류가 있고 지속적으로 개발 보완되고 있다. 이 Garbage Collector들이 이용하는 몇가지 알고리즘을 알아보자.

  • Reference Counting
  • Mark and Sweep
  • Mark and Compact
  • Generational

Reference Counting

동작방식

최상위 진입점인 Root Space에서 Heap 공간을 참조하는 방향으로 순회한다. 이 때 Heap 내부의 Object가 참조될 때 마다 Reference Count가 1씩 증가한다. 만약 Reference Count가 0이면 GC의 대상이 된다.

문제점

  • 순환참조문제: Root Space에서 더 이상 Object를 참조하지 않더라도 Heap 내부의 Object끼리 서로를 참조하게 되면 Reference Count가 항상 1 이상이어서 메모리가 해제되지 않는다.

Mark and Sweep

동작방식

최상위 진입접인 Root Set에서 Heap 내부 공간을 순회한다. 이 때 참조되는 Object를 Reachable Object 그렇지 않은 경우를 Unreachble Object라고 한다. 이렇게 Heap 내부 공간을 순회하며 Reachable, Unreachable로 Object를 표시하는 과정을 Mark이라고 한다. Marking이 완료되면 Unreachable Object를 정리하면된다. 이 과정을 Sweep이라고 한다.

문제점

  • Heap 내부 공간이 크면 시간이 오래걸린다. 이는 시스템 성능저하의 원인이 된다.
  • Sweep이 완료된 후 Object의 메모리 공간이 흩어져 있어 메모리 단편화가 발생한다.

Mark and Compact

동작방식

Mark and Sweep의 메모리 단편화 문제를 해결하기 위해 나온 알고리즘이다. Mark and Sweep을 수행한 후 Compact라는 과정이 추가됐다. Compact 과정은 Sweep 이후 흩어져있던 Reachable Object들을 한 곳으로 모으는 과정을 의미한다. 메모리 공간을 효율적으로 관리할 수 있다는 장점이 있다.

문제점

  • Compaction 이후 살아남은 Object들의 Reference를 업데이트 하는 과정에서 부가적인 Overhead가 수반된다.

Generational

Weak Generational Hypothesis

  • 대부분의 객체는 금방 접근 불능 상태(Unreachable)가 된다.
  • 오래된 객체에서 젊은 객체로의 참조는 아주 드물다.

Generational 알고리즘을 이용한 GC는 위 2가지 가정으로 만들어졌다. 잠깐 사용되고 금세 버려지는 객체를 Young 영역에 할당하고, Young 영역에서 오래 살아남은 객체를 Old 영역으로 이동시켜 관리하는 방식이다.

동작방식 (Hotspot VM)

위 그림은 Hotspot VM의 Heap 내부 구조이다.
GC 동작방식
  1. 객체가 처음 생성되고 Eden 영역에 할당된다. 이 때 객체의 age-bit은 0으로 할당한다.
  2. Eden 영역에 객체가 다 차면 Minor GC가 한번 일어나고 Eden 영역의 객체는 Survivor 영역으로 이동한다. 이 때 S0, S1중 비어있는 곳으로 이동한다. 이 때 Eden 영역에서 Survivor 영역으로 이동한 객체는 age-bit이 1씩 증가한다.
  3. 또 Eden 영역에 객체가 다 차면 Minor GC가 일어나고 Eden과 특정 Survivor 영역에 있던 객체들이 비어있는 Survivor 영역으로 이동한다. 이 때 S0와 S1 사이를 이동한 객체의 age-bit은 1씩 증가한다.
  4. Survivor 영역에서 일정 age-bit에 도달하면 Old Generation으로 이동한다.
  5. Old 영역에 할당한 메모리가 허용치를 넘게되면 Old 영역 전체를 탐색하여 참조되지 않은 메모리를 정리한다. 이 과정을 Major GC라고 한다.

Major GC는 Minor GC보다 훨씬 큰 오버헤드를 수반하므로 개발자가 GC를 튜닝할 때 Old 영역과 Young 영역의 비율을 상황에 맞게 조절할 수 있다.


이번에는 GC가 무엇인지 GC를 수행하는 알고리즘들에 대해 알아봤다. 다음시간에는 Serial GC부터 Z GC까지 GC 변천사에 대해 포스팅하겠다.

profile
백엔드 개발자가 되기위한 여정

0개의 댓글

Powered by GraphCDN, the GraphQL CDN