Java Garbage Collection Basics

노력을 즐겼던 사람·2021년 4월 4일
4

sia 인턴십

목록 보기
3/7
post-thumbnail

JAVA GC에 관한 오라클 공식 문서를 읽어봅시다.
아래에 등장하는 모든 그림과 내용은 오라클 공식 문서에서 가져왔습니다.

Purpose - 이 문서의 목적은 뭘까?

이 문서는 Hotspot JVM에서 동작하는 GC의 기본적인 부분을 커버한다.
GC의 기능들을 배우고 Visual VM로 GC process를 모니터링 하는 방법을 알아보자 (Visual Vm 모니터링은 생략)
참고로 Java SE 7의 Hotspot JVM을 기준으로 한다.

two main goals of tuning

GC 튜닝의 주된 목적은 두가지로 분류 할 수 있다.

  • Responsiveness (민감도) - 시간
  • Throughput (처리량) - 공간

Responsiveness (민감도)

세 가지 예시 상황을 살펴보자

  • 데스크탑 UI에 걸린 이벤트가 얼마나 빠르게 반응 할 것인가?
  • 웹 사이트가 얼마나 빠르게 페이지를 리턴해줄 것인가?
  • 데이터 베이스 쿼리가 얼마나 빠르게 실행될 것인가?

위의 세 예시들은 많은 시간을 소요하면 안된다.
(High pause times are not acceptable)
즉, 요청에 빠르게 응답하는 것이 중요하다.

Throughput (처리량)

특정한 시간에 최대한 많은 양을 처리하는 것에 중점을 둔다.
마찬가지로 세 가지 예시를 살펴보자

  • 특정한 시간에 처리 가능한 트랜잭션의 수
  • 배치 프로그램이 한시간 안에 처리 가능한 작업 수
  • 한시간 안에 처리 가능한 데이터 베이스 쿼리의 수

위의 세 예시들은 많은 시간을 소요해도 된다.
(High pause times are acceptable)
대신 정해진 시간 안에 최대한 많은 작업을 처리하는 것을 목적으로 한다.

Describing Garbage Collection

Automatic garbage collection(AGC)은 힙 메모리에서 볼 수 있는 과정(process) 이다.
무슨 과정이냐면 사용 중인 objects와 사용 중이 아닌 objects를 식별하고 사용 중이 아닌 objects를 삭제하는 과정이다.

사용 중인 오브젝트나 참조 되고 있는 오브젝트들은 우리가 작성한 프로그램이 아직도 가리키고 있는 (maintains a pointer to that object) 오브젝트들을 말한다.

사용 중이 아닌 오브젝트들은 프로그램의 어떠한 부분에서도 더 이상 사용하지 않는 오브젝트들을 말하며 이 녀석들이 점유하고 있던 메모리를 회수 할 수 있다.

Process Step 1. Marking

첫 번째 과정을 Marking이라고 부른다.
Marking은 메모리를 조각 단위로 식별하는 과정이다.
조각의 단위는 사용하고 있는 것과 사용하지 않는 것을 기준으로 한다.

그림 출처: 오라클 공식 문서

그림처럼 사용 중인 것과 사용 중이 아닌 메모리를 구분한다.

Process Step 2. Normal Deletion

Normal Deletion은 사용하지 않는 object 들을 삭제하는 것이다.
당연하지만 참조된 object들과 포인터들은 어딘지 알고 있어야 한다.

memory allocator는 새로운 object들이 할당될 수 있는 빈 공간 블록들을 참조하고 있다.

Process Step 2a. Deletion WIth Compacting

Deletion의 퍼포먼스를 향상시키기 위해서 사용하지 않는 object들을 삭제하는 것 외에도 남은 공간들을 압축할 수 있다.
이 과정을 거침으로써 앞으로 수행될 메모리 할당 작업들이 훨씬 쉽고 빠르게 수행될 수 있다.

Why Generational Garbage Collection?

JVM에 존재하는 모든 object 들을 대상으로 Mark와 compact를 하는 것은 비효율적이다.
할당된 object들의 많아질수록 object들의 리스트들은 점점 길어지고 garbage collection time 도 점점 길어진다.
반면에 empirical analysis of applications (프로그램의 경험적 분석)에 따르면 대부분의 object들의 수명은 짧다.

예시 데이터를 살펴보자. Y축은 아직도 살아있는 바이트의 수를 보여주고 X 축은 특정 시간에 따라 할당된 바이트의 수를 나타낸다.
보다시피 시간이 흐를 수록 할당된 object들은 적어진다. 그러니까 실제로 대부분의 object들이 짧은 수명을 가지고 있다는 것을 그래프를 통해 알 수 있다.

JVM Generations

위에서 봤듯이 object allocation 관련된 동작을 통해 JVM의 퍼포먼스를 증진시킬 수 있다.
사실 힙은 더 작은 부분으로 나뉘어져 있다.

  • Young Generation (젊은)
  • Old Generation (오래된)
  • Tenured Generation (장년)
  • Permanent Generation (영구적인)

Young Generation

새로 할당된 object들이 할당되고 나이를 먹어가는 공간이다.
이 Young generation 공간이 가득차면 Minor collection을 야기한다.
Minor collection은 high object mortality rate를 통해서 최적화 될 수 있다.
Young generation이 죽은 object로 가득차면 collection이 아주 빠르게 이루어진다. 그리고 몇몇 생존한 object들이 나이를 먹으면 결국 old generation으로 옮겨간다. (ㅠㅠ)

Stop the World Event
모든 minor garbage collection들은 "Stop the World" 이벤트이다.
"Stop the World"의 의미는 minor garbage의 collection operation이 완료될 때 까지 어플리케이션의 모든 스레드들이 멈춘다는 것이다.
minor garbage collection은 언제나 Stop the World 이벤트이다.

Old Generation

Old Generation은 장수 object 들을 저장하는데에 사용된다.
일반적으로, young generation에 속하는 object들의 수명 한계치가 설정되고 그 수명 한계치에 충족되면 young generation -> old generation이 발생한다.
old generation를 collect하는 것을 major garbage collection이라고 부른다.

major garbage collection도 Stop the World 이벤트다.
일반적으로 major garbage collection은 느리다. 왜냐면 살아있는 모든 object들을 대상으로 수행되기 때문이다.
이 특징 때문에 major barbage collection의 stop the world 시간은 old generation의 메모리 크기에 영향을 받는다.

Permanent generation

Permenent generation은 JVM이 어플리케이션에서 사용하는 클래스들과 메소드들을 명세하기 위해서 필요한 metadata를 포함한다.
이 공간은 런타임 시점에 사용되는 클래스들로 채워지고 Java SE의 라이브러리 클래스, 메소드들이 여기에 저장될 수 있다.

JVM이 판단하기에 더 이상 필요하지 않거나 다른 클래스를 위한 공간이 필요하다고 판단되면 collect(unload)를 수행할 수 있고 이 공간도 full garbage collection이 발생한다.

The Generational Garbage Collection Process

이제 우리는 힙이 왜 여러 개의 작은 단위로 구분되어 있는지 알게 되었다.
이제 각 영역들이 어떻게 상호작용하는지 살펴볼 시간이다.
아래 그림을 보고 JVM에서 어떻게 object 할당과 object들이 나이를 먹는지 살펴봅시다.

  1. 새로운 object는 eden space에 할당된다. 이때 다른 두 개의 survivor space들은 비어있다.
  2. eden space가 가득차면 minor garbage collection이 시작된다.
  3. 참조되고 있는 object가 첫 번째 survivor space(s0)로 이동한다.
    안참조되고 있는 object들은 삭제된다.
  4. 그리고 2번과 3번이 반복된다. 이때 조금 다른점이 s0에 있던 참조되고 있는 object들은 두 번째 survivor space(s1)로 이동한다. 이것을 나이를 먹는다.. 라고 한다. (aged) 그리고 저번 minor GC에서 s0에 object들을 옮겼다면 이번엔 바로 s1으로 옮긴다. 그리고 eden과 s0이 청소한다.
  5. 다시 2번과 3번이 반복된다. 이번엔 s1에 있는 녀석들을 s0로 옮긴다. 그리고 s1과 eden을 청소한다.
  6. minor GC가 계속 반복된 결과 object의 나이가 설정한 한계치(threshold)에 도달하면 해당 object는 old generation으로 이동한다.
  7. 결국 minor GC에 의해 old generation이 채워지고 old generation이 채워지면 major GC 까지 수행된다.

Java Garbage Collectors

CLI를 통해 GC의 옵션을 변경할 수 있다.
테이블을 통해 옵션들을 살펴보자

SwitchDescription
-XmsJVM이 시작하는 시점에 힙 사이즈를 초기화한다.
-Xmx힙의 최대 크기를 설정한다.
-XmnYoung Generation의 크기를 설정한다.
-XX:PermSizePermanent Generation의 시작 크기를 설정한다.
-XX:MaxPermSizePermanent Generation의 최대 크기를 설정한다.

The Serial GC

Java SE 5, 6의 머신에서는 serial collector를 기본 값으로 사용한다.
Serial GC에서는 minor, major gc가 하나의 가상 CPU를 사용하여 mark-compact 방식으로 collection을 수행한다.
간단하게 말하면 오래된 메모리를 힙의 시작 지점으로 옮기는 방식이다. 그래서 새로운 메모리들은 힙의 끝 부분부터 할당된다. 이 압축 방식은 새로운 메모리 할당이 빠르게 이루어질 수 있도록 한다.

Usage Cases

Serial GC는 적은 pause time을 가질 필요가 없는 어플리케이션이나 클라이언트 스타일의 머신에서 동작하는 어플리케이션에 도입하면 가장 좋다.
이 녀석은 gc 작업에 하나의 가상 프로세서만 사용하지만 오늘날의 하드웨어로는 수백MB의 힙을 사용하는 어플리케이션을 나쁘지 않게 사용할 수 있다.

또, Serial GC는 하나의 머신에서 여러개의 JVM이 동작할 때 좋다. 예를들어 CPU 코어 수보다 많은 JVM을 사용할 때 좋다.
차라리 코어를 하나만 사용할 수 있도록 제한하는게 더 낫다는 입장이다. 각자의 trade-off가 있는 듯 하다.

만약 적은 자원의 임베디드 하드웨어를 사용하고 있다면 Serial GC 사용을 고려해보자.

  • To enable the Serial Collector: -XX:+UseSerialGC

The Parallel GC

멀티 스레드를 사용하여 young generation gc를 수행한다.
기본적으로 N 개의 CPU를 사용하는 호스트에서는 parallel GC가 N개의 gc 스레드를 사용한다. 그리고 사용 스레드는 CLI로 컨트롤할 수 있다.
-XX:ParallelGCThreads=<desired number>

만약 싱글코어 호스트에서는 parallel gc가 호출되더라도 기본 gc가 호출된다.
또, 듀얼코어 호스트에서는 parallel gc가 일반적으로 기본 gc만큼 효율적으로 동작하고 young generation gc의 pause time의 감소가 듀얼 코어 이상의 호스트에서 동작하는 것만큼의 퍼포먼스를 발휘한다.

Usage cases

parallel collector는 throughput collector라고도 불린다.
왜냐면 어플리케이션의 throughput 속도를 향상시키기 위해 멀티 코어를 사용할 수 있기 때문이다.
parallel coleector는 많은 양의 작업을 수행해야하거나 긴 pause가 허용될 때 사용하는 것이 적합하다.
보고서 출력이나 거대한 숫자의 데이터베이스 쿼리를 수행할 때를 예로 들 수 있겠다.

  • -XX:+UseParallelGC
    위의 CLI 옵션을 사용하면 멀티 스레드 young generation collector와 싱클 스레드의 old generation collector를 사용할 수 있다.

  • -XX:+UseParallelOldGC
    위의 CLI 옵션을 사용하면 young generation과 old generation 모두에서 멀티 스레드 collector를 사용할 수 있다. 게다가 compacting collector도 멀티 스레드로 동작한다.
    HotSpot에서는 old generation에서만 compact를 수행한다.
    HotSpot에서는 young generation이 a copy collector로 여겨지기 때문에 compact가 필요없다.
    compacting이란 object들 간의 메모리 공백이 없도록 object들을 이동시키는 것을 말한다.
    garbage sweep 후에도 live object들 사이에 구멍이 남아 있을 수도 있다. 그래서 구멍이 남아 있지 않도록 compacting 한다.
    garbage collector는 non-compacting collector일 수 있다.
    만약 non-compacting collector라면 garbage sweep후에 compaction을 하지 않는다.

THe Concurrent Mark Sweep (CMS) Collector

CMS collector는 tenured generation을 collect한다.
이 녀석은 대부분의 gc 작업을 어플리케이션의 스레드들과 동시에 수행해서 gc로 인한 어플리케이션 정지를 최소화 하는 것을 목표로 한다.

일반적으로 concurrent low pause collector는 live object들을 복사하거나 compact하지 않는다. 즉, live object들이 움직이지 않고 gc 작업을 마친다.
만약 fragmenetation에 문제가 생기면 더 큰 힙을 할당해야 한다.

Note: young generation의 CMS collector는 parallel collector와 같은 알고리즘을 사용한다.

Usage Cases

CMS collector는 적은 일시정지가 요구되고 collector와 resource를 공유해도 되는 어플리케이션에 적합하다.
예를들어 웹 서버 응답이나 데이터베이스 쿼리의 응답 이벤트를 포함하고 있는 데스크탑 UI 어플리케이션에 적합하다.

  • To enable the CMS Collector use:
    -XX: +UseConcMarkSweepGC
  • To set the number of threads use:
    -XX:ParallelCMSThreads=<n>

The G1 Garbage Collector

G1은 Garbage First라는 뜻이다.
G1은 Java 7부터 사용이 가능하게 되었고 CMS collector를 대체하기 위해서 디자인 되었다.
G1은 병렬적이며 동시적이고 이전과는 완전히 다른 방식으로 더 나은 compacting을 수행하는 low pause collector를 제공한다.
이 녀석을 설명하는 것은 이 문서의 범위를 뛰어 넘는다고 한다. 그러니까 basics이 아니란 소리..

  • To enable the G1 Collector use: -XX:+UseG1GC

commands

사용중인 gc 확인하기
java -XX:+PrintCommandLineFlags -version

-XX:G1ConcRefinementThreads=8 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=266520960 -XX:MaxHeapSize=4264335360 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation
openjdk version "11.0.10" 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.10+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.10+9, mixed mode)

jdk 11은 기본적으로 G1 GC를 사용한다.

또 읽어볼거리

D2 포스팅

profile
노력하는 자는 즐기는 자를 이길 수 없다 를 알면서도 게으름에 지는 중

0개의 댓글