본문 바로가기
프로그래밍 공부/Java

JVM의 GC

by 조엘 2021. 10. 3.

안녕하세요조엘입니다! 🎉

 

오늘은 JVM의 GC에 대해 알아보는 시간을 가져볼게요! 💪💪

피드백 환영입니다! 댓글 달아주세요 :)

 

 

*** GC가 동작하는 법 ***

기본적인 GC의 동작은 해당 포스팅에서 다루었어요!

해당 포스팅의 내용에 의존하고 있으니 이를 먼저 읽어보시는 것을 추천합니다 :)

https://papimon.tistory.com/93

 

저는 GC가 처음이라니까요?

안녕하세요! 조엘입니다! "처음이라니까요" 시리즈 열한 번째 토픽은 GC입니다. 🙌🙌 C나 C++을 다룰 때는 메모리 관리를 직접 명시적으로 해줬어요. 하지만 JAVA, JavaScript, Python 등을 쓰면서부터

joel-dev.site

 

Java에서의 GC, 즉 JVM의 GC는 기본적으로 Mark And Sweep 방식으로 돌아가는데요. 

Mark And Sweep 방식으로 GC를 실행할 경우 2가지 특징이 있었어요. 

 

  1. 의도적으로 GC를 실행시켜야 한다. 

  2. 애플리케이션 실행과 GC 실행이 병행된다. 

 

이를 염두에 두고, JVM GC가 필요 없는 동적 메모리 영역을 어떻게 자동 해제하는지 알아봅시다. 

 

 

*** JVM에서 Root Space가 어디야? ***

 

Mark And Sweep 방식

Mark And Sweep 알고리즘은 Root Space로부터 동적 메모리 영역의 객체 접근이 가능한지를 해제의 기준으로 둬요. 

Root Space에서부터 해당 객체에 접근이 가능하다면 메모리에 유지시키고, 

접근이 불가능하다면 메모리에서 지워버리는 방식인 것이죠. 

 

그렇다면 JVM의 Root Space는 어디일까요? 

Heap 영역 메모리에 대한 참조를 들고 있을 수 있는 영역일 텐데요.

JVM Memory 영역 중에서는 다음과 같아요. 

 

  1. Stack의 로컬 변수

  2. Method Area의 Static 변수

  3. Native Method Stack의 JNI 참조

 

그림으로 살펴보면 아래와 같아요. 

노란색 박스 안의 영역이 Root Space

이제 Root Space가 어디인지 알았으니, Mark And Sweep 방식으로 동적 메모리 영역을 관리해주면 되겠네요!

 

하지만 무작정 GC를 실행시켜 메모리 관리를 할 수는 없어요. 

Mark And Sweep 방식을 사용한다면, 서론에서 언급한 다음과 같은 특징을 고려하여 GC를 실행시키거든요. 

 

  1. 의도적으로 GC를 실행시켜야 한다. 

  2. 애플리케이션 실행과 GC 실행이 병행된다. 

 

JVM에서는 어떻게 이러한 Mark And Sweep의 특징을 녹여냈는지 알아봅시다. 

 

 

*** GC 실행의 타이밍 ***

Mark And Sweep의 첫번째 특징, "의도적으로 GC를 실행시켜야 한다" 였어요. 

[의도적으로 GC를 실행시켜야 한다]라는 말은 GC 실행의 타이밍이 정해져 있다는 뜻이에요. 

JVM GC에겐 "이 때 GC 한 번 실행시켜야겠다~"라는 기준이 있다는 것이죠. 

 

JVM GC는 언제 동작하는 걸까요? 이를 알기 위해선 Heap 영역을 조금 더 살펴봐야 해요. 

JVM의 Heap 영역

JVM의 Heap은 크게 두 영역, Young GenerationOld Generation으로 나뉘어요. 
Young Generation에서 발생하는 GC는 minor gc, Old Generation에서 발생하는 GC는 major gc라고 불러요. 

Young Generation은 또다시 세 영역, Eden / Survival 0 / Survival 1 영역으로 나뉘는데요. 
Eden은 새롭게 생성된 객체들이 할당되는 영역이고, 

Survival 영역은 minor gc로 부터 살아남은 객체들이 존재하는 영역이에요. 
Survival 영역에는 특별한 규칙이 있는데요. 

Survival 0 혹은 Survival 1 둘 중 하나는 꼭 비어 있어야 한다는 것이에요. 

 

minor gc의 실행 타이밍은 바로 Eden 영역이 꽉 찼을 때에요. 

아래의 그림과 같은 상황 말이죠. (회색 네모는 메모리에 할당된 객체를 뜻합니다)

Eden 영역이 꽉 찼다!

minor gc가 발생하고 난 뒤 Reachable이라 판단된 객체들은 Survival 0 영역으로 옮겨져요. 아래 그림과 같이요. 

살아남은 객체들의 숫자들이 0에서 1로 변한 것을 알 수 있는데요. 이는 age bit를 뜻해요. 

Minor gc에서 살아남을 때마다 1씩 증가해요. Age bit의 용도는 조금 있다가 나올 거예요. 

살아남은 객체들은 Survival 0 으로 이동 + age bit 1 증가

또다시 Eden 영역이 꽉 차고, minor gc가 발생해요.

이번에 Reachable이라 판단된 객체들은 Survival 1으로 이동합니다. 

Eden 영역이 또 다시 꽉 차고
다음과 같이 Survival 1 영역으로 이동한다

이후 또 Eden 영역이 꽉 차면, minor gc가 발생해요. 

이번에 Reachable이라 판단된 객체들은 Survival 0으로 이동하겠네요!

Eden 영역이 또 꽉차고
이번엔 Survival 0으로!

Survival 0 영역으로 넘어온 객체 중 하나가 어느덧 오래 살아남아 age bit 3이 되었어요.

여기서 age bit의 쓰임새가 밝혀지는데요.

JVM GC에서는 일정 수준의 age-bit를 넘어가면 오래도록 참조될 객체구나 라고 판단하고,

해당 객체를 Old Generation에 넘겨줘요. 이 과정을 Promotion 이라고 해요.

Java 8에서는 Parallel GC 방식 사용 기준 age bit15가 되면 promotion이 진행되어요.

일정 수준의 age bit를 넘기면 Promotion이 진행된다!

시간이 아주 많이 지나면 언젠간 Old generation도 다 채워지는 날이 오겠죠?

이때 Major GC가 발생하면서 Mark and sweep 방식을 통해 필요 없는 메모리를 비워줘요.

Major GCMinor GC보다 더 오래 걸리게 됩니다.

Major GC 발생!

이렇게 heap 영역을 굳이 young generation과 old generation으로 나눈 데에는 이유가 있어요. 
GC 설계자들이 어플리케이션을 분석해보니 대부분의 객체가 수명이 짧다는 것을 깨달았어요. 

대부분의 객체는 금방 사라진다!


GC도 결국 비용인데, 메모리의 특정 부분만을 탐색하며 해제하면 효율적이겠죠?
어차피 대다수의 객체가 금방 사라지니, Young generation 안에서 최대한 처리하도록 하는 것이죠. 

 

 

*** GC 실행 방식 ***

Mark And Sweep의 두번째 특징, "어플리케이션 실행과 GC 실행이 병행된다" 였어요. 

[어플리케이션 실행과 GC 실행이 병행된다]라는 말은 곧,

JVM에서는 어플리케이션과 GC를 병행하여 실행할 수 있는 여러 옵션들을 제공한다는 뜻이에요. 

 

GC가 어떤 방식으로 어플리케이션 실행과 병행되는지 살펴보기 전에, Stop The World 라는 개념을 알고 가야해요. 
Stop The World 란 GC를 실행하기 위해 JVM이 어플리케이션 실행을 멈추는 것이에요. 

 

처음 알아볼 GC 실행 방식은 Serial GC에요. 

 

Serial GC는 하나의 쓰레드로 GC를 실행하는 방식이에요. 
하나의 쓰레드로 GC를 실행시키다 보니 Stop-The-World 시간이 긴 것을 알 수 있어요.
싱글 쓰레드 환경 및 Heap 영역이 매우 작을 때 사용하기 위해 나온 방식이에요. 

 

 

 

 

 

다음으로 알아볼 GC 실행 방식은 Parallel GC에요. 

 

Parallel GC는 여러 개의 쓰레드로 가비지 컬렉션을 수행하는데요. 
따라서 앞선 Serial GC보다 Stop The World 시간이 짧아진 것을 알 수 있어요. 
멀티코어 환경에서 어플리케이션 처리 속도를 향상시키기 위해 사용되어요. 
Java 8에서 기본으로 쓰이는 GC 방식이에요. 

 

 

 

그 다음은 CMS GC이에요. 

 

CMS GC에서 CMS는 Concurrent-Mark-Sweep의 줄임말인데요. 
Stop-The-World 시간을 최소화하기 위해 고안됐어요. 

대부분의 가비지 수집 작업을 어플리케이션 스레드와 동시에 수행하여, Stop The World 시간을 최소화시키는 것이죠. 
하지만 메모리와 CPU를 많이 사용하고, Mark-And-Sweep 과정 이후 메모리 파편화를 해결하는 Compaction이 기본적으로 제공되지 않는다는 단점이 있어요. 

 

 

마지막 G1 GC 인데요. 

G1은 가비지 퍼스트의 줄임말이에요. 

G1 GC는 Heap 영역을 조금 다르게 써요. 
Heap을 일정 크기의 Region으로 잘게 나누어 어떤 영역은 Young Gen, 어떤 영역은 Old Gen으로 써요. 
런타임에 G1 GC가 필요에 따라 영역별 Region 개수를 튜닝한다고 합니다. 
그에 따라 Stop the world를 최소화 할 수 있었다고 해요. 

Java9 이상 부터는 G1 GC를 기본 GC 실행방식으로 사용해요. 

 

 

지금까지 JVM GC의 동작 방식에 대해 알아봤습니다!

읽어 주셔서 감사하고, 오류가 있다면 댓글로 알려주세요 :) 

 

반응형

'프로그래밍 공부 > Java' 카테고리의 다른 글

Reflection API  (4) 2021.06.09
HashMap/HashSet의 원리  (2) 2021.03.13
Java Code Conventions  (0) 2020.11.24

댓글2