1. 미들웨어이야기/01. JVM

JVM GC(Garbage Collection) 이란?

OSSW(Open Source System SoftWare 2009. 6. 3. 16:55

GC 무엇인가?

GC Garbage Collection 약자로 Java 언어의 중요한 특징중의 하나입니다. GC Java Application에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말합니다.
예전의 전통적인 언어 C등의 경우 malloc, free등을 이용해서 메모리를 할당하고, 일일이 메모리를 수거해 줘야 했습니다. 그러나 Java 언어에서는 GC 기술을 사용함에 따라서 개발자로 하여금 메모리 관리에서 부터 좀더 자유롭게 해주었습니다.

GC 동작 방법은 어떻게 되는가?

1.  JVM 메모리 영역

GC
동작 방법을 이해하기 위해서는 Java 메모리 구조를 먼저 이해할 필요가 있습니다. 일반적으로 Application에서 사용되는 객체는 오래 유지 되는 객체보다, 생성되고 얼마 있어서 사용되지 않는 경우가 많습니다. <그림 1 참조>


[그림 1]. 메모리 foot print

그래서 Java에서는 크게 두가지 영역으로 메모리를 나누는데 Young 영역과 Old 영역이 그것입니다. Young 영역은 생긴지 얼마 안된 객체들을 저장하는 장소이고, Old영역은 생성 된지 오래된 객체를 저장하는 장소입니다. 영역의 성격이 다른 만큼 GC 방법도 다릅니다.
먼저 Java 메모리 구조를 살펴보겠습니다
.


[그림 2]. Java 메모리 구조

Java 메모리 영역은 앞에서 이야기한 영역 (Young 영역,Old 영역) Perm 영역 이렇게 3가지로 영역으로 구성됩니다.


[ 1]. Java 메모리 영역

2.  GC 알고리즘

그러면 메모리 영역을 JVM 어떻게 관리하는지에 대해서 알아보겠습니다.
JVM
New/Young 영역과, Old영역 영역에 대해서만 GC 수행합니다. Perm영역은 앞에서 설명했듯이 Code 올라가는 부분이기 때문에, GC 일어날 필요가 없습니다. Perm영역은 Code 모두 Load되고 나면 거의 일정한 수치를 유지합니다
.

- Minor GC
먼저 New/Young영역의 GC방법을 살펴보면 New/Young 영역의 GC Minor GC라고 부르는데, New/Young영역은 Eden Survivor라는 가지 영역으로 나뉘어 집니다. Eden영역은 Java 객체가 생성되자 마자 저장이 되는 곳입니다. 이렇게 생성된 객체는 Minor GC 발생할때 Survivor 영역으로 이동됩니다
.
Survivor
영역은 Survivor 1 Suvivor2 영역 영역으로 나뉘어 지는데, Minor GC 발생하면 Eden Survivor1 Alive되어 있는 객체를 Suvivor2 복사합니다. 그리고 Alive되어 있지 않는 객체는 자연히 Suvivor1 남아있게 되고, Survivor1 Eden영역을 Clear합니다. (결과적으로 Alive 객체만 Survivor2 이동한 것입니다
.)
다음 Minor GC 발생하면 같은 원리로 Eden Survivor2영역에서 Alive되어 있는 객체를 Survivor1 복사합니다. 계속 이런 방법을 반복적으로 수행하면서 Minor GC 수행합니다
.
이렇게 Minor GC 수행하다가, Survivor영역에서 오래된 객체는 Old영역으로 옮기게 됩니다. 이런 방식의 GC 알고리즘을 Copy & Scavenge라고 합니다. 방법은 매우 속도가 빠르며 작은 크기의 메모리를 Collecting하는데 매우 효과적입니다. Minor GC 경우에는 자주 일어나기 때문에, GC 소요되는 시간이 짧은 알고리즘이 적합합니다
.
내용을 그림을 보면서 살펴보도록 하겠습니다
.


[그림 3-1]. 1st Minor GC

Eden에서 Alive 객체를 Suvivor1으로 이동하고. Eden 영역을 Clear합니다.


[그림 3-2]. 2nd Minor GC

Eden영역에 Alive 객체와 Suvivor1영역에 Alive 객체를 Survivor 2 copy하고.
Eden
영역과 Suvivor2영역을 clear합니다
.


[그림 3-3]. 3rd Minor GC

객체가 생성된 시간이 오래 지나면 Eden Suvivor영역에 있는 오래된 객체들을 Old 영역으로 이동합니다.

- Full GC
Old
영역의 Garbage Collection Full GC라고 부르며, Full GC 사용되는 알고리즘은 Mark & Compact라는 알고리즘을 이용합니다. Mark & Compact 알고리즘은 전체 객체들의 reference 따라 가면서 reference 연결되지 않는 객체를 Mark합니다. 작업이 끝나면 사용되지 않는 객체를 모두 Mark 되고, mark 객체를 삭제합니다.<그림 4 참고> (실제로는 compact라고 해서, mark 객체로 생기는 부분을 unmark 사용하는 객체로 메꾸어 버리는 방법입니다
.)
Full GC
매우 속도가 느리며, Full GC 일어나는 도중에는 순간적으로 Java Application 멈춰 버리기 때문에, Full GC 일어나는 정도와 Full GC 소요되는 시간은 Application 성능과 안정성에 아주 영향을 줍니다
.


[그림 4]. Full GC

GC 중요한가?

Garbage Collection중에서 Minor GC 경우 보통 0.5 이내에 끝나기 때문에 큰문제가 되지 않습니다. 그러나 Full GC 경우 보통 수초가 소요가 되고, Full GC동안에는 Java Application 멈춰버리기 때문에 문제가 있습니다.
예를 들어 게임 서버와 같은 Real Time Server 구현을 했을 , Full GC 일어나서 5 동안 시스템이 멈춘다고 생각해보면, 일반 WAS에서도 5~10 동안 멈추면, 멈추는 동안의 사용자의 Request Queue 저장되었다가 Full GC 끝난 후에 요청이 한꺼번에 들어오게 되면 과부하에 의한 여러 장애를 만들 있습니다
.
그래서 원할 서비스를 위해서는 GC 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 변수로 작용할 있습니다.

다양한 GC 알고리즘

앞에서 설명한 기본적인 GC방법 (Scavenge Mark and compact)이외에 JVM에서는 좀더 다양한 GC 방법을 제공하고 동작방법이나 사용방법도 틀리다. 이번에는 다양한 GC 알고리즘에 대해서 알아보면. 현재 (JDK 1.4)까지 나와 있는 JVM GC방법은 크게 아래 4가지를 지원하고 있습니다.

Default Collector

Parallel GC for young generation (from JDK 1.4 )

Concurrent GC for old generation (from JDK 1.4)

Incremental GC (Train GC)

1.  Default Collector

GC 방법은 앞에서 설명한 전통적인 GC방법으로 Minor GC Scavenge, Full GC Mark & compact 알고리즘을 사용하는 방법입니다. 알고리즘에는 이미 앞에서 설명했기 때문에 별도의 설명을 하지는 않습니다.
JDK 1.4
에서부터 새로 적용되는 GC방법은 Parallel GC Concurrent GC 가지 방법이 있습니다. Parallel GC Minor GC 좀더 빨리하게 하는 방법이고 (Throughput 위주) Concurrent GC Full GC시에 시스템의 멈춤(Pause)현상을 최소화하는 GC방법입니다.

 

2.  Parallel GC

JDK1.3까지 GC 하나의 Thread에서 이루어집니다. Java Multi Thread환경을 지원함에도 불구하고, 1 CPU에서는 동시에 하나의 Thread만을 수행할 밖에 없기 때문에, 예전에는 하나의 CPU에서만 GC 수행했지만, 근래에 들어서 하나의 CPU에서 동시에 여러 개의 Thread 실행할 있는 Hyper Threading기술이나, 여러 개의 CPU 동시에 장착한 HW 보급으로 하나의 HW Box에서 동시에 여러 개의 Thread 수행할 있게 되었습니다.
JDK 1.4
부터 지원되는 Parallel GC Minor GC 동시에 여러 개의 Thread 이용해서 GC 수행하는 방법으로 하나의 Thread 이용하는 것보다 훨씬 빨리 GC 수행할 있습니다
.


[그림 7]. Parallel GC 개념도

<그림 7> 보면 왼쪽의 Default GC방법은 GC 일어 Thread들이 작업을 멈추고, GC 수행하는 thread gc 수행합니다. (그림에서 파란영역), Parallel GC에서는 여러 thread들이 gc 수행이 가능하기 때문에, gc 소요되는 시간이 낮아집니다.
Parallel GC
언제나 유익한 것은 아니다. 앞에서도 말했듯이 1CPU에서는 동시에 여러 개의 thread 실행할 없기 때문에 오히려 Parallel GC Default GC 비해서 느립니다. 2 CPU에서도 Multi thread 대한 지원이나 계산 등을 위해서 CPU Power 사용되기 때문에, 최소한 4CPU 256M 정도의 메모리를 가지고 있는 HW에서 Parallel GC 유용하게 사용됩니다
.
Parallel GC
크게 가지 종류의 옵션을 가지고 있는데,Low-pause 방식과 Throughput 방식의 GC방식이 있습니다
.
Solaris
기준에서 Low-pause Parallel GC -XX:+UseParNewGC 옵션을 사용합니다. 모델은 Old 영역을 GC할때 다음에 설명할 Concurrent GC방법과 함께 사용할 있습니다. 방법은 GC 일어날때 빨리 GC하는것이 아니라 GC 발생 Application 멈춰지는 현상(pause) 최소화하는데 역점을 두었습니다

Throughput
방식의 Parallel GC -XX:+UseParallelGC (Solaris 기준) 옵션을 이용하며 Old 영역을 GC할때는 Default GC (Mark and compact)방법만을 사용하도록 되어 있습니다. Minor GC 발생했을때, 되도록이면 빨리 수행하도록 throughput 역점을 두었습니다.
외에도 ParallelGC 수행 동시에 개의 Thread 이용하여 Minor영역을 Parallel GC할지를 결정할 있는데, -XX:ParallelGCThreads= 옵션을 이용하여 Parallel GC 사용되는 Thread 수를 지정할 있습니다.



 

3. Concurrent GC
앞에서도 설명했듯이, Full GC Old 영역을 GC하는 경우에는 시간이 길고 Application 순간적으로 멈춰버리기 때문에, 시스템 운용에 문제가 됩니다. 그래서 JDK1.4부터 제공하는 Concurrent GC 기존의 이런 Full GC 단점을 보완하기 위해서 Full GC 의해서 Application 멈추어 지는 현상을 최소화 하기 위한 GC방법입니다
.
Full GC
소요되는 작업을 Application 멈추고 진행 하는 것이 아니라, 일부는 Application 돌아가는 단계에서 수행하고, 최소한의 작업만을 Application 멈췄을때 수행하는 방법으로 Application 멈추는 시간을 최소화합니다
.


[그림 8]. Concurrent GC 개념도

그림 8에서와 같이 Application 수행중일때(붉은 라인) Full GC 위한 작업을 수행합니다. (Sweep,mark) Application 멈추고 수행하는 작업은 일부분 (initial-mark, remark 작업)만을 수행하기 때문에, 기존 Default GC Mark & Sweep Collector 비해서 Application 멈추는 시간이 현저하게 줄어듭니다.

Solaris JVM에서는 -XX:+UseConcMarkSweepGC Parameter 이용해 세팅합니다.


4. Incremental GC (Train GC)
Incremental GC
또는 Train GC라고도 불리는 GC방법은 JDK 1.3에서부터 지원된 GC방법입니다. 앞에서 설명한 Concurrent GC 비슷하게, 의도 자체는 Full GC 의해서 Application 멈추는 시간을 줄이고자 하는데 있습니다. Incremental GC 작동방법은 간단합니다. Minor GC 일어날때 마다 Old영역을 조금씩 GC 해서 Full GC 발생하는 횟수나 시간을 줄이는 방법입니다.


[그림 9]. Incremental GC 개념도

그림 9에서 보듯이. 왼쪽의 Default GC Full GC 일어난 후에나 Old 영역이 Clear됩니다. 그러나, 오른쪽의 Incremental GC 보면 Minor GC 일어난 후에, Old 영역이 일부 Collect 것을 있고. Incremental GC 사용하는 방법은 JVM 옵션에 -Xinc 옵션을 사용하면 됩니다. Incremental GC 많은 자원을 소모하고, Minor GC 자주 일으키고, 그리고 Incremental GC 사용한다고 Full GC 없어지거나 횟수가 획기적으로 줄어드는 것은 아닙니다. 오히려 느려지는 경우가 많다. 필히 테스트 후에 사용하도록 해야 합니다.

※ Default GC이외의 알고리즘은 Application 형태나 HW Spec(CPU, Hyper threading 지원 여부), 그리고 JVM 버전(JDK 1.4.1이냐 1.4.2) 따라서 차이가 매우 큽니다. 이론상으로는 실제로 성능이 좋아 보일 있으나, 운영 환경에서는 여러 요인으로 인해서 기대했던 만큼의 성능이 나올 있기 때문에, 환경에서 미리 충분한 테스트를 거쳐서 검증한 후에 사용해야 합니다.