본문 바로가기

GC

[GC] GC 시작하기 - 1 (GC 과정, Minor GC)

GC 이론에 대해 차근차근 이해해 볼 예정이다. 기본 내용은 여기를 참고해 기재할 예정이라, 글의 목록 내용등이 거의 일치하겠지만, 내가 이해한 방식으로 다시 글을 작성하기 때문에 근소하게 다를 것이다.

 

하늘색 글자들은, 포인트가 될만한 내용을 표시한 것이고

초록색 글자들은, 추후 또 하나의 주제로 찾아볼만한 내용들에 대해서는 표시한 것이니 참고 바람

 

시작하기

GC에 대해 알아보기전에 알아야 할 용어가 있다. 바로 'stop-the-world'. 여기서 말하는 world는 JVM에서 실행되고 있는 애플리케이션을 뜻한다. 그럼 'stop-the-world'는? GC가 JVM에게 본인의 작업을 수행하기에 앞서 명령을 내린다고 생각하면 된다. stop-the-word가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다.(멋잇는 명령어 구먼) GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작한다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생한다. 대개의 경우 GC 튜닝이라고 하면 이 stop-the-world의 시간을 줄이는것을 말한다.

 

GC가 필요한 이유

보통 Java에서는 개발자가 프로그램 코드로 메모리를 명시적으로 해제하지 않기 때문에

가끔가다 명시적으로 해제하는 경우가 있을 수 있다. 그 경우 해당 객체를 null로 지정하거나 System.gc() 메서드를 호출하는 경우가 있는데, null로 지정하는것은 큰 문제가 안 되지만 System.gc() 메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 끼치므로 절대로 사용하지 말 것을 권한다.

 

GC 탄생 배경

개발자가 명시적으로 메모리를 해제하지 않기에, 가비지 컬렉터(Garbage Collector)가 더이상 필요없는 쓰레기 객체를 찾아 지우는 작업을 해야 한다. 이 가비지 컬렉터는 'wak generational hypothesis'라고 불리는 가설(혹은 전제조건) 하에 만들어 졌다고 한다.

- 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.

- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

 

이 가설의 장점을 최대한 살리기 위해서 HotSpot VM에서는 크게 2개로 물리적 공간을 나누었다. Young과 Old.

* 공간을 크개 2가지로 나누었다고 하였으나, Permanent Generation이라는 영역도 존재해 추가해놓ㅇ

Young Generation 영역 새롭게 생성한 객체의 대부분이 위치하는 곳. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에, 매우 많은 객체가 이 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다고 말한다.
Old Generation 영역 접근 불가능 상태로 되지 않고 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Mager GC 혹은 Full GC가 발생한다고 말한다.

Permanent Generation 영역(*permanent : 영구[영속]적인)

Methoad Area 라고도 하며 객체나 억류(intern, *억류 : 억지로 머무르게 함. '가둠', '잡아둠'으로 순화)된 문자열 정보를 저장하는 곳이며, Old 영역에서 살아남은 객체가 영원히 남아있는 곳은 절대 아니다. 이 영역에서 GC가 발생할 수도 있는데 여기서 GC가 발생해도 Major GC 횟수에 포함된다.

 

영역별 데이터 흐름을 그림으로 살펴보면 다음과 같다.

그림-1. GC 관점에서의 데이터 흐름도

카드 테이블(Card Table)

"Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우가 있을 때에는 어떻게 처리될까?"라고 궁금증을 가질수 있다. 이러한 경우를 처리하기 위해서 Old 영역에는 512바이트의 덩어리(chunk)로 되어 있는 카드 테이블(card table)이 존재한다.

 

카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 카드테이블에 기록(표시) 한다. Young 영역의 GC를 실행할 때에는 이 카드테이블을 참고해서 GC 대상인지 식별한다

그림-2. Old 영역의 카드 테이블

카드 테이블은 write barrier를 사용하여 관리한다. write barrier는 Minor GC를 빠르게 할 수 있도록 하는 장치이다. write barrier 때문에 약간의 오버헤드는 발생하지만 전반적인 GC 시간은 줄어들게 된다.

 

Young 영역의 구성

객체가 제일 먼저 생성되는 Young 영역은 총 3개의 영역으로 나뉜다.

- Eden 영역

- Survivor - A 영역

- Survivor - B 영역

* Survivor A,B의 의미는 단순히 2개 라는것을 표현하고자 붙인것이지 순차적인 의미를 갖고있지 않음

 

각 영역의 처리 절차를 순서에 따라서 기술하면 다음과 같다.

  1. 새로 생성한 객체는 Eden 영역에 위치
  2. Eden 영역에서 GC가 한번 발생한 후 살아남은 객체는 Survivor A,B 영역 중 하나로 이동
  3. Eden 영역에서 GC가 발생할 때 마다, Survivor 영역으로 객체가 계속 쌓임
  4. Survivor 영역 중, 하나라도 가득 차게 되면 그 중에서 살아남은 객체를 나머지 하나의 Survivor 영역으로 이동
  5. 가득 찼던 Survivor 영역은 비움
  6. 이 과정을 반복하면서, 계속 살아남은 객체는 Old 영역으로 이동

위 절차대로, Survivor 영역 중 하나는 반드시 비어있는 상태로 남아 있어야 한다. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 모두 사용량이 0 이면 그 시스템은 정상적인 상황이 아니라고 판단할 수 있다.

 

이렇게 Minor GC를 통해서 Old 영역까지 데이터가 쌓인것을 그림과 나타내면 다음과 같다.

그림-3. Minor GC 과정의 전/후 비교

[참고] HotSpotVM에서 사용하는 메모리 할당 기술

HotSpot VM에서는 보다 빠른 메모리 할당을 위해 bump-the-pointer와 TLABs(Thread-Local Allocation Buffers)라는 두가지 기술을 사용한다.

* 아래 표는, 원글만 보고 정리한 것이라 추후 좀더 찾아볼 필요가 있음

기술 원리 장점 단점
bump-the-pointer Eden 영역에 할당된 마지막 객체를 추적한다. 마지막 객체는 Eden 영역의 맨 위(top)에 있다. 그리고 다음에 생성되는 객체가 있으면, 해당 객체의 크기가 Eden 영역에 넣기 적당한지만 확인한다.  새로운 객체를 생성할 때 마지막에 추가된 객체만 점검하면 되므로 매우 빠르게 메모리 할당이 이루어진다. 멀티 스레드 환경에서는 Thread-safe 하기 위해 락(lock)이 발생할 수 밖에 없고, lock-contention 때문에 성능이 매우 떨어짐
TLABs 멀티 스레드 환경에서, 각각의 스레드가 각자의 몫에 해당하는 Eden 영역의 작은 덩어리를 가질 수 있도록 하여, 각 스레드는 자기가 갖고있는 TLAB에만 접근할 수 있기에 lock 없이 메모리 할당이 가능함 -

 

참고

GC의 첫 이해 : https://d2.naver.com/helloworld/1329

 

 

'GC' 카테고리의 다른 글

[GC] GC 시작하기 - 2(Old 영역)  (0) 2019.07.07