본문 바로가기
Java & Kolin

Garbage Collection

by Limm_jk 2021. 4. 4.
이 글은 Oracle의 Java Garbage Collection Basic,
그리고 JAVA SE 16의 HotSpot Virtual Machine Garbage Collection Tuning Guide 을 읽고 정리한 글입니다.

Garbage Collection이란?

Garbage Collection은 힙 메모리를 체크하여 사용되지 않는 객체를 확인하고, 삭제하는 프로세스입니다. C나 C++같은 언어가 수동으로 malloc이나 free로 메모리를 관리해주어야 하는 것에 달리 GC가 있는 언어는 각 언어의 Virtual Machine에서 관리해줍니다.

 

이러한 메모리 관리는 Reference Counting, Mark-and-Sweep 등등 많은 방법으로 이루어지지만, 오늘은 Oracle Hotspot에서 이야기 해주는 Garbage Collection을 위주로 공부해보겠습니다. 

 

Mark-and-Sweep이 이루어지는 과정

1. Marking

다음과 같이 Garbage Collection이 시작되면 사용 중인 메모리와 그렇지 않은 메모리를 식별하여 마킹합니다.

위 그림에서는 사용되지 않은 객체들이 주황색으로 칠해졌습니다.

(그림과는 달리 실제는 사용되는 객체가 마킹됩니다.)

 

이 시점에서 사용 중인 메모리와 그렇지 않은 메모리를 참조의 여부로 판단합니다.

아래 그림을 통하여 보다 쉽게 이해할 수 있을 것 같습니다.

위 그림처럼 GC에는 GC root라는 것이 존재하는데, 이 root를 통해 순회하며 참조 여부를 마킹합니다.

 

위 그림에서 빈 노드가 주황색으로 색칠된 객체와 같다고 생각 할 수 있습니다.

 

2. Sweep - 삭제

위에서 주황색으로 칠해진(또는 마킹되지 않은) 객체를 삭제하는 과정에서 아래와 같이 삭제할 수 있습니다.

하지만 이렇게 삭제할 경우 외부단편화가 발생하여 충분한 메모리 공간이 남아있음에도 실제로 할당할 수 없는 문제가 발생합니다.

 

그래서 삭제하는 과정에서 정리하고 남은 조각들을 병합해주는 Compaction 작업을 함께 진행해주면 이후에도 보다 효율적으로 메모리를 관리할 수 있습니다.

세대별 GC

하지만, 이렇게 모든 객체를 매번 순회하고 삭제하는 일은 비효율적인 일입니다. 실행이 길어지며, 점점 더 많은 객체가 할당이 될 것이며 GC에 걸리는 시간이 굉장히 길어질 것입니다. 

 

이를 해결하기 위하여 경험에 기한 가설을 세웁니다.

The most important of these observed properties is the weak generational hypothesis, which states that most objects survive for only a short period of time.

대부분의 객체는 아주 짧은 시간만 살아남는다는 가설을 담은 Weak Generational Hypothesis를 기반으로 세대별 Garbage Collection을 제안합니다.

 

세대별로 GC를 운영하기 위하여 다음과 같이 Heap을 나누어 사용합니다.

그리고 세대별로 GC를 실행하는 scope에 대하여 Minor / Major Garbage Collection으로 나뉩니다.

 

GC가 수행되는 과정을 알아보며 각 세대에 대한 이해를 해보겠습니다.

 

1. 새로운 객체의 할당

새로운 객체가 할당이 됩니다. 새로운 객체는 Eden(태초의 영역)에 배치가 됩니다.

그림에서는 그렇지 않지만, 현재 두 Survivor space는 비어있다고 가정하겠습니다.

 

2. Eden의 가득참

현재의 객체가 투입되면서 Eden이 가득차게 되었습니다. 이때, Minor Garbage Collection이 발생합니다.

Minor Garbage Collection은 Young Generation에서만 일어나는 Garbage Collection을 의미합니다.

 

또한, 현재 S0와 S1은 비어있다고 가정합니다.

 

3. Minor Garbage Collection

위에서 설명했던 Sweep이 발생하면서 참조관계가 남아있는 객체들에 마킹을 하고 이 객체들을 첫번째 Survivor 공간(S0)로 이동시킵니다. 그리고 참조되지 않은 객체들은 Eden을 비우며 삭제됩니다.

 

4. Aging

이후, 지속적으로 Minor Garbage Collection이 일어납니다. 

Young Generation을 GC하는 과정에서 Survivor 공간에서 살아남은 객체들을 빈 Survivor 공간(S1)으로 이동시킵니다. 또한 Eden에서 살아남은 객체들 또한 Survivor 객체들이 이동한 Survivor 공간으로 이동합니다.

 

이 결과, 지속적으로 Minor Garbage Collection이 일어나도 하나의 Survivor공간은 비어있게 됩니다.

 

그리고 이 과정을 반복하며 객체의 Aging을 1씩 증가시키게 됩니다.

 

5. Promotion

위의 과정을 반복하며 Aging을 지속적으로 증가시키게 됩니다. 

반복을 거치며 특정 Aging 값(위 그림에서는 8에 해당됩니다.)에 도달한다면 Old Generation(Tenured)으로 승격하게 됩니다.

 

예외가 존재하는데, Survivor 공간이 꽉차게 되는 경우 보다 빨리 승격하는 경우도 존재합니다.

 

Old Generation은 자주 발생하는 Minor Garbage Collection에 해당되지 않는 객체입니다.

즉, 위에서 대부분의 객체는 아주 짧은 시간만 살아남는다는 가설 중 "대부분"에 해당되지 않는 객체라고 볼 수 있습니다.

 

6. Major Garbage Collection

위 과정이 반복되며 결국 Old Generation 또한 꽉차게 되는 시점이 오게 될 것입니다.

 

이때, Old Generation에 대하여 Garbage Collection이 이루어지게 되며, 이렇게 Old Generation만 GC를 수행하는 작업을 Major Garbage Collection이라고 합니다.

 

Major Garbage Collection은 보통 Minor Garbage Collection보다 느린데, 이는 Minor Garbage Collection에 비하여 큰 공간의 전체를 탐색하기 때문입니다.

 

이 Major Garbage Collection은 Minor Garbage Collection과 밀접한 관계가 있는데, 이는 가득차면 수행되는 GC의 특성 상, 대부분의 경우에서 Minor Garbage Collection에 의해서 Major Garbage Collection이 트리거되기 때문입니다.

 

 

정리하며

사실 GC에 처음 관심을 가진 것은 Java가 아닌 Python과 Pypy를 공부하며 접했었는데, Java 진영의 GC docs가 강력해서 Java를 따라서 정리해보았습니다.

 

이후로는 최신 Java SE 버전인 16에서 지원하는 Garbage Collector의 종류를 정리해보려 합니다.

 

잘못된 점이 있다면 편하게 말씀해주시면 감사하겠습니다 :)

댓글