쓰나미꿈
366
2017-03-10 00:57:20 작성 2017-03-10 01:10:41 수정됨
4
9448

[JAVA] 가비지 컬렉터의 배경과 종류


안녕하세요! 쓰나미꿈입니다.

저번 글에서는 GC의 개념에 대해서 간단하게 설명을 했었는데요,

이번에는 GC의 배경과 종류에 대해서 정리해볼까 합니다.

이번 글에도 많은 의견과 비판 부탁드립니다~ 꾸벅(_ _)

 

1.     Generational GC

먼저 Generational GC에 대해서 알아보겠습니다.

Genrational의 사전적 의미는 ‘세대의’, ‘세대 간의’ 라는 의미입니다.

Generational GC 에 대해서 어떻게 해석해야 할 지 모르겠습니다만, (혹시 국내에서 쓰이고 있는 용어나 번역이 있으면 알려주시면 감사하겠습니다.) 의역을 해보자면 ‘세대 별로 나누어 진행되는 가비지 컬렉터’ 정도(?)가 될 것 같습니다.

 


(갑자기 뜬금 오글오글 우화)

JVM 이라는 나라에 힙(heap)이라는 마을이 있습니다.

이 마을은 오랜 시간동안 객체들이 세대를 거듭하여 발전되고 유지되어 왔습니다.

그런데 어느 날 한 여행자가 이 마을을 지나치게 되었는데 온통 노인 밖에 보이지 않았습니다.

이를 이상하게 여긴 여행자는 한 노인(Old) 객체를 붙잡고 그 이유를 물었습니다.

“객체 어르신, 이 마을에는 왜 젊은이(Young)들이 거의 보이지 않는 것인가요?

오랜 기간 전쟁이 있었나 봅니다.”

객체 어르신이 말하길 “전쟁이라니.. 이 마을은 무척 평화롭다네. 다만 전설에 따르면 쥐씨(GC)라는 쥐 같은 괴물이 저 숲에 살고 있다고 하는데, 매 년 나타나 그 쥐씨가 젊은 것들만 잡아먹고 사라져버리지 뭔가…”.

여행자 왈 “그럼 이 마을의 모든 어르신들이 맞서 싸우면 되지 않습니까? 그걸 보고만 계셨단 말입니까”.

“(절래절래)그 쥐씨는 마법을 쓴다네, Stop the world!!! 라고 외치는 순간, 쥐씨를 제외한 모든 것들이 멈추어 버려… 지금은 젊은 것들만 노리지만 우리도 언제 잡아먹힐지 모르는 운명이라네.”

 

David ungar(응가??..)라는 분이 84년 ‘Generation Scavenging: A Non-disruptive High Performance Storage Reclamation Algorithm’이라는 한 논문을 발표합니다.

이 논문으로 ACM에서 2008년 상까지 받게 되는데요, 여기서 가설을 하나 제시하면서 Generational GC를 소개합니다. “대부분의 객체는 일찍 죽는다.” 라는 가설입니다.

실제 통계로도 생성된 객체의 98%의 객체가 곧바로 쓰레기 객체가 된다고 합니다.


이러한 경험적 사실들을 바탕으로 Generational GC가 디자인 됩니다.

힙을 Young Generation 영역과 Old Generation 영역으로 나눈 뒤에, Young Generation영역을 주기적으로 청소하고, 상대적으로 오랜 기간 사용되는 객체를 Old Generation으로 보내버리는 것이 기본 원리입니다.

이렇게 함으로써 GC는 매번 힙 전체를 청소할 필요가 없이 Young 위주로 하다가, Old 영역에 공간이 부족하게 될 때만 Old영역을 청소를 하게 됩니다.


 

이러한 디자인에는 성능 상 이점이 두 가지가 있습니다.

첫 번째로, Young Generation 은 Old Generation 보다 사이즈가 작고, 힙 공간의 일부분이기 때문에 GC가 전체 영역을 처리하는 것보다 시간이 덜 걸립니다.

즉, stop-the-world 로 애플리케이션이 중지되는 시간이 짧아집니다.

(하지만 자주 GC가 작동하게 되는데, stop-the-world로 길게 한 번 멈추는 것보다 짧게 여러 번 멈추는 것이 더 이익이라고 하네요.)


두 번째는 Young Generation 영역을 한 번에 모두 비우기 때문에, 이 Young Generation 부분에 해당하는 연속된 여유 공간이 만들어 집니다(Compacting 이라고 합니다).

만약, GC가 군데 군데 골라서 객체를 제거했다면, 메모리 파편화(memory fragmentation)이 발생하여 연속된 큰 데이터가 들어갈 공간이 부족해지겠죠.

 


위의 우화에서 쥐씨(GC)가 언젠가 젊은이들 뿐만 아니라 우리(노인)들도 잡아먹을 수 있다고 암시하는 부분이 나오는데요, Young Generation 영역에 있던 객체들이 Old Generation 영역으로 계속 옮겨지다 보면 언젠가 Old Generation 영역도 가득 차는 날이 오겠죠?

이 날이 노인 객체들의 제삿날입니다. 물론 쓸모있는 객체들(참조되고 있는 객체)은 살아 남겠지만요.


 

여기서 중요한! 용어 정리를 한 번 하겠습니다.

GC가 Young Generation 영역을 정리하는 것을 마이너(minor) GC라고 하고 Old Generation 영역을 정리하는 것을 풀(full) GC라고 합니다.

(면접이나 시험에서 출제될 수 있으니 이 용어들은 꼭 기억해주세요!!!)




그럼 이제 GC의 종류에 대해서 알아보겠습니다.


2. GC의 종류

GC는 Serial, Parallel, CMS, G1(Garbage First) GC 라는 이름으로 4가지 형태가 존재합니다.

일단 GC가 Young Generation 영역을 청소할 때(마이너 GC)는 이 4가지 종류에 상관없이 모두 stop-the-world 상태가 발생합니다.

(헷갈리지 마세요. 마이너 GC에는 장사 없습니다)

하지만 Old Generation 영역을 청소하는 방법은 서로 다릅니다.

즉, 어떻게 다르게 청소하느냐(알고리즘)에 따라 위에서 말씀드린 4가지 종류로 구분이 됩니다.


 

Serial Garbage Collector

가장 간단한 GC 입니다.

주로 32비트 JVM에서 돌아가는 싱글쓰레드 어플리케이션에서 사용이 됩니다.

(특별히 지정하지 않으면 기본 GC로 설정이 되어있습니다.)

단순 무식해서 Young Generation 영역을 정리(process)하는 마이너 GC 때도 올 스탑(stop-the-world),

풀 GC할 때도 올 스탑입니다.

그리고 동작하는 GC도 싱글쓰레드로 돌아갑니다.

이런 GC라면 서버 어플리케이션에서 사용하기엔 좀 부담이겠죠?

그냥 데스크탑 같이 클라이언트에서 혼자 돌아가는 어플리케이션에서나 사용할 법 합니다.

 


Parallel Collector(=Throughput Collector)

이 GC는 64비트 JVM이나 멀티 CPU 유닉스 머신에서 기본 GC로 설정이 되어있습니다.

그리고 마이너 GC와 풀 GC 모두 멀티쓰레드를 사용합니다.

위의 Serial GC보다 훨씬 빠르겠죠?

여러쓰레드가 작동하기 때문에 이름이 Parallel 입니다.

하지만 마이너, 풀 GC 모두 올 스탑인건 Serial GC와 같습니다.

(그렇다면, 다음에 나올 GC는 어떤 기능이 향상되어 있을지 예상이 가시죠?)

 


CMS Collector

어떻게 하면 풀 GC의 stop-the-world 상태를 좀 줄여볼 수 있을까? 라는 고민에서 출발한 GC.

Parallel Collector와 같이 멀티쓰레드로 마이너 GC를 합니다.

그리고 이 순간에는 stop-the-world가 발동합니다.

(그러나, 마이너 GC 알고리즘 자체는 Parallel과 다릅니다.)

하지만…!

풀 GC는 거의 stop-the-world가 발생하지 않습니다. 과연 어떻게 해결했을까요?

(개념은 단순하게)어플리케이션이 작동하는 중에,

백그라운드에서 쓰레드를 만들어서 Old Generation 영역에 살고있는 쓸모없는(참조되지 않고있는) 객체들을 지속적으로 제거합니다.

즉,  CMS의 장점은 stop-the-world 가 거의 없다는 것!

마이너 GC에서 잠깐씩, 그리고 풀 GC에서는 거의 발생하지 않습니다.

백그라운드에서 항상 일을 하고 있으니까요.

그렇다면 단점은 뭐가 될까요?

2가지가 있습니다.

일단 백그라운드에서 항상 GC 쓰레드가 돌아야하니, CPU 리소스를 많이 잡아먹는다는 것.

그리고 자꾸 중간 중간에 Old Generation에 있는 객체들을 쏙쏙 잡아먹으면 메모리가 군데군데 비어지게 되므로,

메모리 파편화가 발생합니다.

만약 CPU 리소스가 부족해진다거나, 메모리 파편화가 너무 심해서 메모리 공간이 부족해지면 Serial GC가 하던 짓을 똑같이 따라합니다.

그건 바로, Old Generation 영역을 싱글쓰레드로 청소하기…

 

G1 Collector

힙 영역이 매우 큰 머신(최소 4GB)에서 돌리기에 적합한 GC입니다.

대신 CMS의 단점을 어느 정도는 극복해냈습니다.

힙에 영역(Region) 이라는 개념을 도입한 것인데요, 힙을 여러 개의 Region으로 나눕니다.

몇 몇 Region 은 Young Generation 영역으로 쓰이고, 나머지 몇 몇 Region 은 Old Generation 영역으로 쓰입니다.

Young Generation 영역을 정리하는 건 Parallel이나 CMS처럼 멀티쓰레드로 정리를 합니다.

(뭐 마찬가지로 지울건 지우고, 계속 쓰이고 있는건 Old Generation 영역으로 옮기겠죠.)

그리고, Old Generation 영역에 해당하는 Region이 여러 개 있을 텐데 CMS처럼 백그라운드 쓰레드로 이 영역들을 정리를 합니다.

그런데 CMS와 차이점은 중간 중간 쓸모없는 객체들을 쏙쏙 빼먹는게 아니라.

한 Region을 통째로 정리해 버립니다.

참조가 없는 객체들은 지우고, 사용 중인 객체는 다른 Region으로 고스란히 복사를 합니다.

이 다른 Region으로 사용 중인 객체만 옮기는 과정에서 차곡차곡 옮기므로 Compacting이 되므로 메모리 파편화 현상이 생기지 않는 것이죠!

그렇다면 CMS의 문제점이었던 CPU리소스를 많이 차지한다(1) 그리고 메모리파편화(2) 중에 메모리파편화를 해결한 GC가 되겠네요.


 

지금까지 GC의 배경과 종류에 대해서 정리를 해보았습니다.

GC는 공부하면 공부할수록 끝이 없네요.

다음에 기회가 되면 자바 코드 상에서 GC를 관리하는 법이나 GC 튜닝에 대해서 다루어 보겠습니다.

(개인적으로는 이제 쓰레드에 대해서도 좀 정리를 해보고 싶습니다 ㅎㅎ)

 


지금까지 부족한 글 읽어 주셔서 감사드립니다.

설명을 돕는 이미지가 있으면 참 좋을 것 같은데, 이 부분이 많이 아쉽습니다.

 


문제점이나 보완사항은 언제든지 댓글로 달아주세요!(도와주세요!)

그럼 모두 좋은 하루 되시기 바랍니다. 바이~

 

14
12
  • 댓글 4

  • LichKing
    15k
    2017-03-10 09:25:40
    안그래도 요즘 GC에 대해 좀 알아보고있었는데 마침 좋은글이네요 ㅎㅎ 감사합니다
    0
  • orth
    667
    2017-03-10 12:07:27
    0
  • suri
    15
    2017-03-11 00:16:14

    글 잘봤습니다.

    감사합니다 ^^

    0
  • 베이스키타
    1k
    2017-03-15 09:28:09

    재미잇는내용 이해하기가 쉽네요

    덕분에 잘보고갑니다~

    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.