티스토리 뷰

728x90
반응형

포스팅은 Garbage Collector에 대한 고찰을 다루고자 합니다.


GC는 JAVA가 제공하는 특 장점 중 Number.1으로 뽑을 수 있을 정도로 중요한 기능입니다. 개발자는 개발을 진행하며 GC를 고민하고 비즈니스 로직을 구현한다면 시스템은 보다 안정적일 것이며, 보다 좋은 시스템이라 일컬을 수 있을 것입니다.


먼저 GC가 발생될 수 있는 가능성에 대해 살펴보도록 하겠습니다.

gc는 말그대로 Garbage Collection을 수행하여 불필요한 Obejct를 Memory 상에서 제거하는 것을 의미합니다.

여기서 중요한 점은 바로 이 과정이 자동으로 이루어 진다는 점입니다.

개발자는 Java 프로그램의 GC 과정에 관여하기 위해 Object를 Null로 지정하거나, System.gc()를 통해 직접 해제하는 경우가 있습니다.

이때 System.gc()의 경우 FullGC로 분류되어 STW(Stop The World)가 발생됩니다. 개발자가 JVM의 모든 구조를 파악하고 최적의 위치를 찾아 강제 GC를 넣는 경우는 사실상 없다고 봐야 합니다.

따라서 System.gc()를 넣는 건은 성능상 마이너스 효과를 초래한다는 것을 인지하고 개발간 이를 사용하는 것은 지양해야 합니다.


자 우리는 GC의 최적값을 찾아 내기 위해서는 JVM이 어떻게 구성되어 있고 어떻게 동작하는지 판단하는 것은 매우 중요한일입니다.

지금부터는 일반적인 JVM의 구성인 Young 영역, Old 영역에 대한 간단한 개념 정립을 수행하고 넘어가도록 하겠습니다.

- Young 영역 : 새로운 객체가 생성되는 위치입니다. 대부분 hit률이 낮은 새로운 객체들은 Young 영역에 쌓였다가 Tenuring Threshold 만큼 사용되지 못하면 Minor GC에 의해 제거됩니다.

- Old 영역 : Tenuring Threshold 만큼 살아 남은 Object의 경우 Young 영역에서 Old 영역으로 이동됩니다. Old 영역은 GC가 발생할 경우 FullGC가 발생하여 STW가 발생되며 이로 인한 JVM 멈춤현상이 생깁니다. 일반적으로 GC 튜닝을 위한 대상은 Old 영역입니다.


그럼 어떤 방식으로 Object들이 움직이게 되는지 살펴보겠습니다.

먼저 Object가, Allocation이 되면 Young Generation에 해당 Object가 위치하게 됩니다.

Object가 Young에서 Old Generation 영역으로 이동되는 것은 Tenuring Threshold에 의해 살아남은 객체로 한정되며, 이를 Promotion되었다고 합니다.

또한 PermGen의 경우 (JDK 1.7 이상에서는 MetaSpace) 객체나 intern 문자열 정보등이 저장되는 곳으로, 일반적으로는 Meta 정보가 저장되는 공간이라 일컷습니다.


자 그럼 Old영역에 있는 객체가 Young 영역의 객체를 참조해야 하는 경우에는 어떻게 처리가 될까요?

이러한 경우에는 Old 영역에 있는 512 바이트의 chunk card table을 사용하게 됩니다.

card table에서는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시되게 되는데, Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 table를 검색하여 GC 대상인지를 식별하게 됩니다.


그럼 이제 본격적으로 GC의 구성에 대해 살펴보도록 하겠습니다.

사실 본 포스팅은 앞선 포스팅에서 살펴 보았는데요.

[GC] Log 분석 가이드 (HotSpot 계열 JDK) http://waspro.tistory.com/209

[GC] Log 분석 가이드 (IBM 계열 JDK) http://waspro.tistory.com/210

간략히 되짚어 보도록 하겠습니다.


먼저 Young 영역입니다.


최초 생성되는 Object 들이 저장되는 공간으로 Eden, Survivor 영역으로 구성되어 있습니다.

다시 Survivor는 From과 To로 이루어져 있으며, Eden에서 살아남은 Object는 From으로 이동합니다.

이후 From에서 살아남은 객체는 To로 이동되면 To를 From으로 From을 To로 변경하여 이를 반복 수행합니다.

Hit(GC에서 살아남은 횟수)가 Tenuring Threshold 만큼 수행된 Object들은 Old 영역으로 이동됩니다.

자세한 처리 과정은 위 포스팅을 확인하시면 될것 같습니다.


HotSpot VM에서는 보다 빠른 메모리 할당을 위해서 두 가지 기술을 사용합니다. TLABs와 bump-the-pointer라는 기술이 바로 그것입니다.

- bump-the-pointer는 Eden 영역에 할당된 마지막 객체를 추적하여 메모리 할당을 하는 방식입니다. 하지만 해당 방식은 Thread-Safe하지 않아 Lock-Contention을 발생 시킬 가능성이 높아 오히려 성능이 떨어지게 됩니다.

- TLABs는 각각의 Thread가 각각의 몫에 해당한느 Eden 영역의 작은 덩어리를 관리하도록 하는 것입니다. 자신의 Thread Local Allocation만 관리하여 bump-the-pointer를 사용하더라도 Lock Contention으로부터 자유로워 메모리 할당에 이점이 있습니다.



다음으로 Old 영역입니다.


Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행합니다. GC Policy에 따라 어떠한 방식으로 gc를 수행할 것이지가 결정되어 이는 성능에 커다른 영향을 끼치게 합니다.


지금부터는 5가지의 gc policy에 대해 살펴보도록 하겠습니다.

- Serial GC (-XX:+UseSerialGC) : Serial GC는 적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식

mark & sweep & compact를 기준으로 GC를 수행하는 알고리즘 방식입니다.

Mark는 살아있는 객체 즉 gc 대상이 아닌 객체에 대해 식별하는 역할을 합니다.

Sweep은 Heap의 앞 부분부터 mark 된 Object를 제외하고 제거합니다.

Compact는 Sweep 이후 비어있는 Heap 공간들을 연속되게 쌓이도록 힙의 앞 부분부터 채우는 과정입니다.

- Parallel GC (-XX:+UseParallelGC) : Serial GC오 기본적인 알고리즘은 같지만 여러개의 Thread가 나누어져 처리하는 방식

Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하며, Throughput GC라고 부릅니다.

- Parallel Old GC (-XX:+UseParallelOldGC)

Parallele GC와 비교하여 Old 영역의 GC 알고리즘만 차이가 있습니다.

기존 Mark - Sweep - Compaction 단계에서 Parallel Old GC는 Mark - Summary - Compaction 단계를 거칩니다.

Summary 단계는 앞서 GC를 수행한 영역에 대해서 별도록 살아 있는 객체를 식별한다는 점에서 차이가 있습니다.

- CMS GC (-XX:+UseConcMarkSweepGC)

Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾아 냅니다. 따라서 초기에 STW가 발생되는 시간이 매우 짧게 형성되어 이점을 가져 올 수 있습니다.

Concurrent Mark 단계에서는 Initial Mark에서 확인된 객체에서 참조하고 있는 객체들을 따라가면서 확인을 하게 됩니다.

Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊어진 객체를 확인합니다.

마지막으로 Concurrent Sweep 단계에서는 GC를 수행하는 작업을 실행합니다.

특징은 Concurrent Mark / Concurrent Sweep을 수행하는 과정에서 다른 쓰레드 들이 실행되고 있는 상황에서 진행된다는 것이 성능상 이점을 가져 옵니다.

CMS는 STW가 짧다는 장점과 더불어 다음과 같은 단점이 존재합니다.

a. 다른 GC 방식보다 메모리와 CPU를 더 많이 사용한다.

b. Compaction 단계가 기본적으로 제공되지 않는다.

결국 초기 STW를 줄일 수 있지만, Compaction이 없어 조각난 메모리가 많아지만 오히려 STW가 늘어날 수 있다는 단점을 보유하고 있습니다.

- G1GC

G1GC는 메모리를 바둑판처럼 각각의 영역으로 구분하고 각 영역에 객체를 할당하여 GC를 실행합니다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행합니다. 즉 기존의 Young, Old 영역에서 진행하는 메모리 처리 방식이 한 영역에서 모두 담당한다고 이해하면 됩니다.

G1GC는 장기적으로 문제가 야기될 가능성이 있는 CMS GC의 대체 방안으로 고안되었으며, 성능상 뛰어나다는 장점이 있습니다.



GC를 다루는 일은 어떻게 보면 해당 시스템의 성능을 크게 향상 시킬 수 있는 방법이자, 반대로 반감 시킬 수 있는 요소입니다.

정확한 이해와 해당 시스템에 적용해 충분한 테스트를 거쳐 결정된 GC Policy는 사이트를 오픈하는데 커다란 도움이 될 것입니다.

본 포스팅을 준비하며 테스트를 거쳐 오픈한 다양한 사이트들의 경험을 비루어 볼때 한 사이트에서 최적을 값을 찾았다고 다른 사이트에서 해당 옵션을 그대로 사용하는 것은 정말 무모하다는 것을 알 수 있었습니다.

이글을 읽는 모든 분들께 드리는 조언은 GC를 다루는 능력을 배양하되 해당 능력은 각 사이트에 국한되어져야 한다는 것입니다.

감사합니다.

728x90
반응형