Java: JDK 7의 새로운 기능 - G1 가비지 콜렉터

JDK 7은 G1이라는 이름의 새로운 가비지 콜렉터를 제공한다. 기존에 잘 사용하던 CMS 가비지 콜렉터도 있는데 왜 또 하나의 새로운 가비지 콜렉터를 제공하는 것일까?

Java가 가장 많이 활용되고 있는 서버 측 응용프로그램들은 대부분 최대 처리량이 매우 중요하다. 하지만, 통신 업체나 금융 업체의 특정 사용 예에서는 실시간 특성도 또한 중요하다.

G1 가비지 콜렉션은 가비지 콜렉션 수행 시 멈춤 시간을 최소화하여 실시간 특성을 향상하면서도 최대 처리량을 희생하지 않는 것을 목표로 한다. G1 가비지 콜렉터는 대용량의 메모리와 다중 프로세스를 제공하는 서버 플랫폼에서 사용되는 것을 가정하고 구현되었다.

이미 JDK 6 update 14에서 -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC 옵션을 제공하면 G1 가비지 콜렉터를 사용할 수 있다. 인터넷 블로그를 뒤져보니 누군가 JDK 6 버전에서 테스트해본 결과 아직 실험 버전이라 그런지 성능 개선 효과가 별로라고 하는데 어떤 시스템에서 테스트를 수행했는지는 확실치 않다. (앞에서 언급한 것처럼 G1은 대용량 메모리와 다중 프로세서를 가진 서버 시스템에 적합하기 때문에 테스트 환경이 어떤 시스템이었는지가 중요할 것이다.)

G1 가비지 콜렉터가 어떤 식으로 동작하는지 이해하기 위해서는 research paper를 읽어보면 가장 확실하겠지만 머리도 아프고 시간도 없으니 만만한 JavaOne 세션을 살펴보는 것이 더 좋으리라.

JavaOne 세션에서 이해한 내용을 기준으로 G1 가비지 콜렉터에 대한 기술적인 백그라운드를 정리해보도록 하겠다. (이 글에서 사용한 모든 그림은 JavaOne 2008 - The Garbage First Garbage Collector 세션의 자료에서 발췌한 것임을 밝힌다.)

G1 가비지 콜렉터의 특징

CMS 가비지 콜렉터를 대치할 G1의 주요 특징을 집어보자. JavaOne 세션 자료를 보면 아래와 같이 G1의 특징들을 나열하고 있다.
  • Server style GC
  • Parallel
  • Concurrent
  • Generational
  • Good Throughput
  • Compacting
  • Ease of use
  • Predictable
Parallel과 Concurrent는 언뜻 동일한 특징으로 보이는데 아래 그림을 보면 그 차이를 확실히 알 수 있다.


위 그림에서 초록색 선은 애플리케이션이 수행하는 쓰레드를 나타내고 붉은색 선은 가비지 콜렉션을 수행하는 쓰레드를 나타낸다. 붉은색 선이 세로로 길게 2줄로 그어진 상태 부분은 모든 애플리케이션 쓰레드가 멈추고 가비지 콜렉션만 일어나고 있는 상태를 의미한다. (Stop the world 라고 표현한다.)

3번째 열을 보면 가비지 콜렉션 쓰레드와 애플리케이션 쓰레드가 50:50 으로 시간을 나누어 실행하는 모습을 보이고 있는데 이런 방식이 Concurrency 지원 방식이다.
5번째 열에서는 위 2개 쓰레드가 모두 가비지 콜렉션을 수행하고 다른 쓰레드는 애플리케이션 동작을 처리하고 있는데 이런 방식을 Parallel이라고 한다.

Generational 가비지 콜렉터 방식은 최근에 생성된 객체가 더 빨리 사라질 수 있다는 가정과 새로 생성된 객체가 오래된 객체에 대한 참조를 가질 확률이 더 높다는 가정에 근거하여 가비지 콜렉션 성능을 높이는 방법이다. 코드 예를 보며 이해를 해보자.

void calculateScore(int score) {
   ReferenceScore s = new ReferenceScor();
   s.setRealtimeIndex(this.realTimeIndexProvider);
   ReferenceValue v = s.getReference(score);
   
   return v.getValue();
   // 함수가 반환되면서 s, v 객체는 유효하지 않다. 즉, 가비지 콜렉션 될 수 있다.
}

위 예제 코드를 보면 2개의 객체가 생성되었고 s 객체가 realTimeIndexProvider라고 하는 기존 객체의 참조를 사용한다. 새로 생성된 객체는 원하는 결과를 얻은 후 함수 범위를 벗어나면서 유효하지 않게 된다.
이처럼 Java 코드를 작성하다 보면 많은 경우 최근에 생성한 객체가 가비지 콜렉션의 대상이 될 확률이 높을 것이다. (물론, 특정 애플리케이션은 이와 다를 수도 있지만 대부분 그렇다는 것이다.)

지금까지 설명한 특징들은 기존 CMS 컬렉터에서도 같았다. Compacting, Ease of use, Predictable등이 CMS 대비 G1에서의 새로운 특징들이다. 즉, 메모리의 단편화를 방지하기 위하여 compaction을 지원하고 가비지 콜렉션의 예측 가능성을 높여 soft한 real-time 특성을 제공하는 것이 G1의 장점이다. 그렇다면, G1 가비지 콜렉터가 실제 어떤 방식으로 동작하여 이러한 특징을 가질 수 있는지 그 내부를 들여다보자.

G1의 Young Generation GC 처리 방법

우선, 가비지 콜렉션에 대한 설명을 이해하기 위해 GC Heap 메모리에 대한 범주를 정의해보겠다.


앞으로 사용할 그림에서 회색은 빈 영역, 밝은 녹색은 young generation (즉, 최근 생성된 객체가 존재하는 영역), 밝은 파란색은 old generation (즉, 생성된 후 어느 정도 기간을 살아남은 늙은(?) 객체들이 존재하는 영역)을 의미하며 어둔운 녹색은 young generation에 있던 객체가 새로운 영역의 young generation으로 복사된 것, 어두운 파란색은 young/old generation에 있던 객체가 새로운 old generation 영역으로 복사된 것을 나타낸다.

그럼, G1 가비지 콜렉터가 young generation 영역에 대해 가비지 콜렉션을 어떻게 수행하는지 그림으로 살펴보자.
G1 가비지 콜렉터의 heap 메모리는 위 그림에서 보이는 것처럼 영역(region)으로 나누어진다. JDK 7에서 영역의 기본 크기는 1MB이고 각 영역에서의 메모리 사용은 compaction되어 언제나 연속적인 메모리 공간을 제공한다. 초록색으로 보이는 영역은 현재 young generation 메모리 공간으로 사용된다. 물리적인 메모리에서 이 속성은 불변하는 것이 아니며 가비지 콜렉션이 일어나는 과정에 young/old/free 영역의 물리적 위치가 변경될 수 있다.


Young generation에 대한 가비지 콜렉션이 수행되면 메모리 영역의 young generation에 해당하는 영역에서 살아남을 수 있는 객체들을 빈 메모리 공간으로 이동한다. 이 때 이동하는 객체의 나이(살아남은 횟수)에 따라 새로운 young 혹은 old generation으로 이동시킨다.


young generation 가비지 콜렉션이 수행되고 나면 위 그림에서처럼 young generation 객체들이 차지하고 있던 메모리가 빈 영역으로 변하고 살아남은 객체들을 복사한 영역에 새로운 young 혹은 old generation 메모리 영역이 잡힌다.

이처럼 young generation만을 별도로 가비지 콜렉션하는 이유는 앞에서도 언급한 것처럼 새로 생성된 객체들이 더 빨리 소멸하고 또한, 새로운 객체를 참조하고 있는 다른 객체가 존재할 가능성이 작기 때문이다. 즉, 가비지 콜렉션을 수행하고 나면 old generation에 있는 객체들보다 더 많이 빈 영역을 만들 가능성이 크다. 또한, 상대적으로 작은 영역에 대해서만 가비지 콜렉션을 수행하므로 성능도 높일 수 있다.


G1의 Old Generation GC 처리 방법

Young generation이 꽉 차서 메모리가 부족하다면 old generation에 대해서도 가비지 콜렉션을 수행해야 한다. old generation의 경우는 어떤 방식으로 처리되는지 살펴보자.


Old generation GC가 수행되면 현재 사용 중인 영역에서 객체가 얼마나 살아남을 수 있는지 계산한다. 만일 특정 영역에 속한 모든 객체가 소멸한 경우라면 (위 그림에서 X 표시한 곳) 해당 영역의 메모리를 모두 빈 영역으로 만든다. 이 과정을 marking 단계라고 한다.


marking 단계가 끝나면 위 그림처럼 모든 객체가 소멸된 영역의 메모리는 빈 영역이 되고 각 영역에 얼만큼의 객체가 살아남았는지 계산된 결과를 가지게 된다.

G1 가비지 콜렉터의 또 하나의 큰 특징은 old generation에 대해 별도의 가비지 콜렉션 과정이 없이 marking 과정에서 얻은 영역에 대한 정보로 young generation GC 처리 시 살아남은 객체가 적은 old 영역도 함께 처리한다는 것이다. 위 그림이 그 과정을 보여주고 있다. young generation GC가 처리되는 과정에 기존 marking 과정에서 얻은 정보를 이용하여 살아남은 객체가 적은 old 영역을 함께 처리한다.

young generation GC가 완료된 후 메모리 공간은 위와 같이 새롭게 잡힌 young / old 영역이 있고 기존에 있던 모든 young generation 영역이 빈 영역이 된다. 몇몇 old generation 영역이 빈 영역으로 바뀐다. 가비지 콜렉션에서 살아남은 old generation 영역은 꽤 많은 객체가 아직 유효한 공간들일 것이다.

이러한 처리 방법으로 G1 가비지 콜렉터는 기존 CMS에 비하여 예측 가능한 가비지 콜렉션 특성을 보여준다. 하지만, 이 방법 또한 완전한 hard real-time 특성을 보이는 것은 아니며 soft real-time 특성을 가진다고 할 수 있다. 만일, 완벽한 real-time이 필요한 경우라면 (군사용 미사일 제어 등과 같은 경우) Java Real Time System을 이용하는 것이 좋겠다.

References:

댓글

이 블로그의 인기 게시물

Wireless: HotSpot 2.0 이란?

Apple M1 Mac Mini에서 이더리움 (Ethereum) 채굴하기

Java: Java for Game? Java가 Game 개발에 어울릴까?