훌륭한 자바 개발자가 되기 위한 소양 기르기 (2) Java Garbage Collection

김성혁·2022년 9월 23일
0

Stop the world란?

GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것

Stop the world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘고, GC 작업을 완료한 이후에 중단했던 작업을 재개한다. 그래서 대개의 경우 GC 튜닝의 관건은 Stop the world 시간을 줄이는 것이다.

가비지 컬렉터의 역할

더 이상 필요 없는 (쓰레기) 객체를 찾아 지우는 것

JVM의 물리적 공간 구성 - Young 영역과 Old 영역

JVM 메모리의 물리적 공간은 두 영역으로 나뉘는데 이를 Young 영역과 Old 영역으로 구분짓는다.

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

GC 영역별 데이터 흐름


위 그림의 Permanent Generation 영역(이하 Perm 영역)은 보통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들이 저장되는 공간으로 흔히 메타데이터 저장 영역이라고도 한다. 이 영역은 Java 8 부터는 Native 영역으로 이동하여 Metaspace 영역으로 변경되었다. (다만, 기존 Perm 영역에 존재하던 Static Object는 Heap 영역으로 옮겨져서 GC의 대상이 최대한 될 수 있도록 하였다) Old 영역에서 살아남은 객체가 영원히 남아 있는 곳은 절대 아니다. 이 영역에서 GC가 발생할 수도 있는데, 여기서 GC가 발생해도 Major GC의 횟수에 포함된다. (변경된 이후에도 Major GC의 횟수에 포함되는가? 찾아보기)

(참고) JDK 8부터 Perm 영역은 삭제되고 Metaspace 영역으로 전환

Java 7Java 8
Class 메타 데이터저장저장
Method 메타 데이터저장저장
Static Object 변수, 상수저장Heap 영역으로 이동
메모리 튜닝Heap, Perm 영역 튜닝Heap 튜닝, Native 영역은 OS가 동적 조정
메모리 옵션-XX:PermSize-XX:MaxPermSize-XX:MetaspaceSize-XX:MaxMetaspaceSize

Native 영역이란 무엇인가?

Native 메모리는 OS 레벨에서 관리하는 영역이고, Heap 영역은 JVM에 의해 관리된 영역이다.

왜 Perm이 제거됐고 Metaspace 영역이 추가된 것인가?

Metaspace가 Native 메모리를 이용함으로서 개발자는 영역 확보의 상한을 크게 의식할 필요가 없어지게 되었다.

그렇다면 "Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우가 있을 때에는 어떻게 처리될까?"

이러한 경우를 처리하기 위해서 Old 영역에는 512바이트의 덩어리(chunk)로 되어 있는 카드 테이블(card table)이 존재한다.

카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시된다. Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC 대상인지 식별한다.

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

write barrier란 무엇인가?

파일시스템의 메타데이터가 올바르게 기록되고 디스크에 제대로(심지어 디스크 전원이 나갈지라도) 반영되게 하기위한 커널 매커니즘

Young 영역

객체가 제일 먼저 생성되는 영역

Young 영역은 Eden 영역과 2개의 Survivor 영역으로 나뉜다.

각 영역의 처리 절차

  • 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
  • Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
  • Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
  • 하나의 Survivor 영역이 가득차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 된다.
  • 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.
  • (Survivor 영역 중 하나는 반드시 비어있는 상태로 남아 있어야 한다.)

Minor GC 전과 후


Old 영역

Young 영역에서 살아남은 객체는 Old 영역으로 이동한다. Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행

GC 방식은 JDK 버전의 업그레이드에 따라 다양한 알고리즘이 등장하였다.


Serial GC (-XX:+UseSerialGC)

운영 서버와 같은 멀티 쓰레드 환경에서는 절대 사용하면 안 되는 방식이며, 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식. Young 영역의 GC는 앞에서 설명한 방식을 사용하고, Old 영역의 GC는 Mark-Sweep-Compact 알고리즘 방식을 사용한다.

  • Mark-Sweep-Compact 알고리즘이란?
    사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업인 Mark 그리고 Mark 단계에서 사용되지 않는 메모리를 해제하는 작업인 Sweep 거기에 힙 영역에 유효한 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분 존재하지 않는 부분으로 나누는 작업인 Compact

Parallel GC (-XX:+UseParallelGC)

Parallel GC는 Serial GC와 기본적인 알고리즘은 같지만 이를 Parallel하게 수행한다. 그렇기 때문에 빠르게 객체를 처리할 수 있다. 멀티 프로세서 또는 멀티 스레드 머신에서 중간 규모부터 대규모의 데이터를 처리하는 애플리케이션을 위해 고안되었으며, 옵션을 통해 애플리케이션의 최대 지연 시간 또는 GC를 수행할 스레드의 갯수 등을 설정해줄 수 있는 특징이 있다. 하지만 Parallel GC 역시도 가비지 수집을 하는 동안 애플리케이션이 멈추는 Stop The World를 피할 수는 없다는 단점이 존재한다.


Parallel Old GC (-XX:+UseParallelOldGC)

앞서 설명한 Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다르다. 이 방식은 Mark-Summary-Compaction 단계를 거친다. Summary 단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아 있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep 단계와 다르며, 약간 더 복잡한 단계를 거친다.


Concurrent Mark Sweep GC (-XX:+UseConcMarkSweepGC)

초기 Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다. 따라서, 멈추는 시간은 매우 짧다. 그리고 Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것이다.

그 다음 Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다. 마지막으로 Concurrent Sweep 단계에서는 쓰레기를 정리하는 작업을 실행한다. 이 작업도 다른 스레드가 실행되고 있는 상황에서 진행한다.

이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.

반면, 다른 GC 방식보다 메모리와 CPU를 더 많이 사용하고 Compaction 단계가 기본적으로 제공되지 않는다. 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.

JDK 9부터는 deprecated됨.


G1 GC (XX:+UseG1GC)

G1 GC는 지금까지 공부한 Young 영역과 Old 영역과 달리 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다.

G1 GC는 성능 효율성이 높기 때문에 CMS GC를 대체할 수 있다. 다른 GC와는 달리 G1 컬렉터는 힙을 동일한 크기의 힙 영역 세트로 분할하며, 각각의 힙 영역은 가상 메모리의 연속된 공간을 차지한다.

애플리케이션마다 성능 최적을 낼 수 있는 GC 알고리즘이 다르기 때문에 항상 하나의 알고리즘이 최적의 효과를 낼거라고는 생각하지 말자. 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다.


NAVER D2

JDK 8에서 Perm 영역은 왜 삭제됐을까

Write Barrier란 무엇인가?

JVM Garbage Collectors

1개의 댓글

comment-user-thumbnail
2022년 9월 26일

소통해요

답글 달기