기본 콘텐츠로 건너뛰기

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 이란?

스마트폰 사용자가 HotSpot 2.0을 지원하는 Wi-Fi 망을 사용하는 경우라면 기존 Wi-Fi 망과 달리 이동통신 망에서 Wi-Fi 망으로의 네트워크 연결 전환이 자연스럽게 이루어진다. 예를 들면, 3G 네트워크를 이용하여 영화를 보고 있다가 HotSpot 2.0 네트워크에 연결이 가능하게 되면 영화 시청 중단 없이 Wi-Fi 망으로 자연스럽게 네트워크 연결이 이동하여 3G 망의 부하도 줄이고 사용자의 네트워크 비용도 절약할 수 있다. 시스코에서 제공한 White Paper 를 참고.

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

 돈을 벌 목적은 아니고 이더리움 기술에 대한 호기심에 직접 채굴(마이닝)에 나서 보기로 했다. 머신은 Apple M1 Mac Mini. 스팩을 살펴보니 8 Core GPU에 16GB 메모리를 공유하고 있어 가능은 해보인다. 큰 흐름은 다음과 같다. 채굴한 이더리움을 저장할 지갑을 만든다 만든 지갑의 정보를 잘 보관해둔다 (Secret Recovery Phrase, 지갑의 주소 값) Apple M1용 채굴 프로그램 설치 내 지갑 정보를 이용해서 채굴 프로그램 실행 일단, 채굴한 이더리움을 저장할 지갑(wallet)을 만들어야 한다.  크롬 브라우저 익스텐션 설치로 비교적 간단하게 지갑을 만들 수 있는  https://metamask.io/ 를 이용하기로 했다. 크롬 익스텐션을 설치 후 기존에 만든 지갑이 없으므로 "Create a Wallet"을 선택한다. 패스워드 입력하고 등등의 절차를 거치면 아래와 같은 Secret Recovery Phrase가 나온다. 이 값을 잘 보관해두기 바란다. 나중에 지갑을 복구할 때 필요한 값이다. 이 값이 유출되면 지갑에 모아둔 이더리움을 다 털릴 수 있으므로 안전한 곳에 보관한다. Confirm Your Secret Phrase에서 확인 과정을 거친다. 직접 입력하는 것이 아니라 단어 별 버튼을 일일이 클릭해서 확인해주어야 한다. (좀 번거롭지만 그만큼 Secret Recovery Phrase가 중요함을 인지시키기 위한 과정이다.) 이제 지갑은 준비 완료. 생성된 Account 화면에서 지갑의 주소갑을 얻을 수 있다.  Apple M1용 채굴 프로그램을 설치해보자. Ethminer M1 Github 프로젝트 에서 미리 컴파일된 바이너리를 다운로드 받는다. (Assets를 펼치고 ethminer-m1을 클릭해서 다운 받으면 된다) 원하는 폴더에 파일을 옮겨 놓고 Terminal에서 chmod +x로 실행가능하게 만든다. % mv ~/Downloads/ethminer-m1 .             %   % c

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

Java가 기업용 서버 소프트웨어 개발에 활발하게 쓰이는 것과 달리 일반 응용프로그램 분야에서는 별로 대접을 받지 못하는 현실을 개선하려면 어떤 분야부터 손보면 좋을까? 로딩타임, 성능, 사용자 인터페이스 등 Java를 이용한 클라이언트 프로그램을 개발하지 않는 이유들은 개발자 별로 서로 다를 것이다. 하지만, 이런 단점에도 불구하고 점점 복잡해지는 소프트웨어를 더 쉽게 다양한 환경에서 동작하도록 만들기 위해서는 Java만큼 이미 성숙한 해결책도 없지 않은가? 클라이언트 개발을 활성화하기 위해 Java를 게임 개발에 활용할 수 있도록 지원하면 어떨까? 역시, 사용자가 직접 쓰는 응용프로그램 중에는 게임이 가장 시장이 큰 분야이니 말이다. 그렇다면, 현재 게임 개발에 Java가 어느 정도 사용되고 있고 미래에 더 활성화 될 가능성은 있을 것인가? 이런 의문점을 가지고 "Java + Game"에 대해 조사해보기로 했다. 1. Java로 개발한 게임들 우선, Java로 개발한 게임들에 대해 살펴보자. Oracle의  Java in Action 웹페이지 를 보면 Java를 이용한 3D MMORPG RuneScape 에 대한 설명이 있다. 가입자가 1억 3000만명이 넘는다고 하다. 실제 게임을 설치해서 실행해보니 WOW같은 화려한 그래픽에는 못미치치만 잘만든 Role Playing 게임이다 Puppy Games 에서 개발한  Revenge of the Titans 게임도 눈여겨 볼 만하다. 이 회사는 작고 손쉽게 즐길 수 있는 게임을 개발하는데 모든 게임을 Java로 만든다. Revenge of the Titans는 Starcraft와 같은 전략 게임으로 너무 머리쓰지 않고 즐길 수 있는 게임이며 그래픽도 신선하다. 개발이 진행 중인 것으로 보이는 Urban Galaxy 라는 게임도 재미있을 것 같다. SF 영화를 보면 자주 등장하는 미래의 빌딩 숲을 날아다니는 자동차로 전투도 치르고 무역도 하며 캐릭터를 키우는 게임으로