새소식

프로그래밍 언어/JAVA

자바 기초 - 3. Garbage Collection (GC)와 Heap 영역

  • -

🎈 Garbage Collection이란?

JVM의 Garbage Collecter 메모리 누수를 방지하기 위해 주기적으로 Heap 영역에서 더이상 참조되지 않는 불필요한 메모리(Garbage)를 청소하는 과정입니다. 

 

✨ GC의 중요한 개념

Garbage Collection이 어떻게 작동하는지 알기 위해서 알고 넘어가야할 개념들이 있습니다.

 

Reachability (접근 가능성)

GC에서 객체가 참조되고 있는지 아닌지(Garbage인지)를 판별하는 것을 의미합니다.

 

객체로부터 유효하게 참조되고 있으면 Reachable, 참조되지 않으면 Unreachable 이라 합니다.

 

Root Set(메소드 영역, 스택, 네이티브 스택)에서 참조되고 있거나 참조의 시발점이 Root Set이라면 Reachable로 보며 그 외의 참조나 참조되지 않는 객체들은 Unreachable, 즉 Garbage로 판단하여 정리하게 됩니다.

 

출처 : https://d2.naver.com/helloworld/329631

 

보통은 개발자가 Reachability를 조절하여 사용하는 경우는 드물지만 필요하다면 java.lang.ref 패키지의 Reference관련 클래스등을 사용하여 Reachable 객체들을 자세히 구별하여 GC 때의 동작을 다르게 지정할 수 있습니다. 자세한 내용은 이곳 에서 찾아보면 좋을 것 같습니다.

 

 

Stop-the-world

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

 

GC를 실행하는 쓰레드를 제외한 나머지 쓰레드들은 모두 작업을 멈추고 GC 작업이 완료된 이후에 중단했던 작업을 다시 시작합니다.

 

모든 쓰레드가 정지되기 때문에 GC가 자주 일어난다면 성능이 저하됩니다. 그렇기 때문에 명시적으로 System.gc()를 호출하여 GC를 한다면 시스템 성능에 큰 영향을 끼칠 수 있으므로 되도록이면 사용하지 않는 것이 추천됩니다.

 

GC에는 특정 알고리즘들이 사용되는데, 어떤 GC 알고리즘을 사용하더라도 Stop-the-world는 발생되므로

GC 튜닝이란 이러한 stop-the-world 시간을 줄이는 것에 목적이 있습니다.

 

 

🎨 GC 알고리즘

그렇다면 GC가 어떻게 동작하는지에 대해 알아보려고 합니다. 앞서 이야기 했듯이 GC에서는 특정한 알고리즘들을 사용하게 되는데 기본적으로 적용되는 두가지 알고리즘에 대해 알아봅시다.

 

Mark & Sweep Algorithm

Mark & Sweep 알고리즘은 이름과 같이 두가지 과정을 거치게 됩니다.

 

Root Set으로부터 참조된 객체를 마크하는 과정인 Mark Phase

 

마크되지 않은 객체들을 추적하여 메모리에서 해제하는 Sweep Phase

 

Sweep 과정을 거치고 나면 객체가 위치해있던 부분이 텅 비어 메모리에 단편화(Fragmentation)가 발생하게 됩니다.

 

 

Mark & Compact Algorithm

Mark & Sweep Algorithm의 단편화 문제를 해결하기 위해 사용되는 것이 Mark & Compact Algorithm 입니다.

 

이 알고리즘은 3단계로 이루어져있습니다.

 

Mark -> Sweep -> Compact

 

여기서 Compact란 메모리를 정리하는 과정을 의미합니다.

Compact 단계에서 분산된 객체들을 Heap의 시작 주소로 모아 압축하게 됩니다.

 

 

 

🔎 Weak Generation Hypothesis

위에서 알아본 알고리즘들은 두가지 가설을 바탕으로 태어나게 되었습니다.

 

  1. 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
  2. 오래된 객체에서 새로운 개체로의 참조는 아주 적게 존재한다.

 

Heap 영역

이러한 가설의 장점을 최대한 살리고 효율적인 GC를 위해 JVM의 Heap 영역에는 두 개의 영역이 존재합니다. 

 

Young Generation

새롭게 생성된 객체가 할당되는 영역으로 2개의 영역으로 구분됩니다.

 

  • Eden : 새로 생성된 객체가 할당되는 영역
  • Survivor 0,1 : 최소 1번의 GC 이후 살아남은 객체가 존재하는 영역

 

Eden 영역이 꽉 차게되면 Minor GC가 발생하게 되는데 이 과정에 대해 자세히 알아보도록 합시다.

 

Minor GC

Young Generation에서 이루어지는 GC를 Minor GC라고 합니다.

 

Minor GC는 아래와 같은 과정을 거치게 됩니다.

 

  1. 새로 생성된 객체가 Eden 영역에 위치
  2. Eden 영역이 가득 차게되면 Minor GC가 발생
    • 참조되지 않는 객체의 메모리 해제 및 살아남은 객체는 Survivor 0, 1 중 객체가 존재하는 쪽으로 이동
  3. Survivor 영역이 가득차면 Survivor 영역의 살아남은 객체를 다른 비어있는 Survivor 영역으로 이동
    • Survivor 영역 중 하나는 반드시 비어있는 상태를 유지해야 하며, 모두 데이터가 존재하거나 모두 사용량이 0이라면 비정상으로 간주합니다.
  4. 위 과정을 반복하며 계속해서 살아남은 객체는 Old 영역으로 이동 (Promotion 과정)

 

 

Old Generation

Young 영역에서 살아남은 객체가 복사되는 영역으로 대부분 Young 영역보다 크게 할당됩니다.

 

크기가 큰 만큼 GC는 적게 발생하며 Old 영역에서 발생하는 GC를 Major GC(= Full GC) 라고 합니다.

 

 

Major GC

Old Generation 에서 발생하는 GC를 뜻합니다.

 

일반적으로 Minor GC보다 오래 걸리며 오래 걸릴수록 어플리케이션에 치명적인 영향을 끼칩니다.

 

 

Old 영역이 Young 영역을 참조하는 경우

간혹 Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우가 있을 수 있습니다.

 

이런 경우를 위해 Old 영역에는 512byte의 Chunk로 되어있는 Card Table이 존재하고 있습니다.

Old 영역의 객체가 Young 영역의 객체를 참조할 때 마다 그에 대한 정보를 Card Table에 표시하게 됩니다.

 

Young 영역에서 GC가 실행될 때, Old 영역의 모든 객체의 참조를 확인하지 않고 Card Table에서만 확인하여 GC 대상인지 식별하여 처리하게 됩니다.

 

 

📜 여러가지 GC 방식

크게 4가지 방식이 존재하고 있습니다.

 

Serial GC

가장 단순한 방식의 GC로 싱글 쓰레드로 동작합니다.

 

싱글 쓰레드로 동작하는 만큼 다른 GC에 비해 Stop-the-world 시간이 깁니다.

Mark & Compact 알고리즘을 사용하며 적은 메모리와 CPU 코어 개수가 적은 상황이 아닌 이상 잘 사용되지 않습니다.

Parallel GC

싱글 쓰레드의 성능 문제를 극복하기 위해 멀티 쓰레드를 사용한 방식입니다.

 

Young 영역의 GC를 멀티 쓰레드 방식을 사용하며 처리하기 때문에 Serial GC에 비해 상대적으로 Stop-the-world가 짧습니다. 

 

Java 8의 기본 GC 방식입니다.

 

Old 영역같은 경우, Parallel Old GC 방식부터 멀티쓰레드 방식을 사용하기 시작했습니다.

 

CMS GC

Concurrent Mark Sweep GC의 약자로 Stop-the-world의 발생을 최소화하고 시간을 줄이고자 만든 GC입니다.

 

4가지 단계를 거쳐 이루어지게 됩니다.

 

  1. Initial Mark : Root Set이 참조하는 객체를 마킹
  2. Concurrent Mark : stop-the-wolrd 없이 참조하는 객체를 따라가며 계속해서 마킹
  3. Remark : 앞의 과정에서 변경사항이 없는지 다시 한번 마킹
  4. Concurrnet Sweep : stop-the-wolrd 없이 접근할 수 없는 객체를 제거

 

 

G1 GC

앞선 GC방식들에서 더욱 발전된 방식으로 현재 GC 중 stop-the-world 시간이 가장 짧습니다.

 

CMS GC를 개선하여 만든 GC로 앞선 GC들과는 다른 구조를 가지고 있습니다.

 

Heap 영역을 Region 단위로 나누어 메모리를 관리하며, 전체 Heap 영역을 탐색하는 것이 아닌 Region 단위로 나누어 탐색하며, 각각의 Region에만 GC가 발생합니다. 

 

G1 GC

Java 9부터 기본 GC로 사용되고 있습니다.

 

 

 

 

참고자료

https://d2.naver.com/helloworld/329631

https://d2.naver.com/helloworld/1329

https://memostack.tistory.com/229

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.