꼬물꼬물

Java Garbage Collection 본문

스터디

Java Garbage Collection

멩주 2022. 10. 14. 20:18

✍️ GC(가비지 컬렉션)이란 무엇인가요?

  • 자바에서 동적 메모리 할당을 통해 Heap 영역에 있는 객체를 더이상 참조하지 않을 때, JVM의 가비지 컬렉션이 접근 불가 상태가 된 메모리를 정리한다.
  • 크게 young, old 영역으로 나뉘는데
  • young: 할당된 객체가 금방 접근 불가 상태가 될 것이라고 생각하며 새로 생성된 객체가 메모리를 차지한다.
  • old: young 영역에서 오래 접근 가능 상태가 유지된 객체들을 복사해 놓는 곳이다.

 

가비지 컬렉션 과정

  • stop-the-world: GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것.
  • stop-the-world가 발행하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 작업을 멈춘다. GC 작업이 완료된 수, 중단했던 작업을 이어간다.
  • 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생한다.
  • GC 튜닝을 통해 stop-the-world의 시간을 줄인다.

 

<메모리 해제>

  • Java는 프로그램 코드에서 메모리를 명시적으로 지정해 해제하지 않는다.
    • C에서는 했어야 했다.. 너무.. 귀찮고.. 복잡한 일.. 자바 짱👍
  • 명시적으로 해제하기 위해서 null을 지정하거나 System.gc()를 호출할 수 있다.
    • System.gc()는 절대 사용하면 안된다..!! 한번 해봐야지.. 
    • 시스템 성능에 매우 큰 영향을 미친다.

Java에서는 가비지 컬렉터(Garbage Collector)가 더 이상 사용하지 않는 객체를 찾아 지우는 작업을 대신해준다.

 

<가비지 컬렉터의 가설 (weak generational hypothesis) >

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

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

HotSpot JVM이란?
가장 일반적인 JVM의 구현 중 하나로 말그대로 Hot한 Spot에서 JIT 컴파일러를 사용한다.

HotSpot JVM: https://velog.io/@aki/HotSpot-JVM

 

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

 

<영역별 데이터의 흐름>

GC 영역과 데이터 흐름도

Permanent Generation은 Method Area라고도 한다.

Method Area: JVM의 구성 중 하나로 클래스 파일을 읽어 클레스 멤버변수, 메서드 등을 넣어둔다.

이 영역에서도 GC가 발생할 수 있는데, 여기서 발생한 GC는 Major GC의 횟수에 포함된다.

 

 

Old 영역의 객체가 Young 영역의 객체를 참조하는 경우에 어떻게 될까?

 

  • 가비지 컬랙션 가설의 부정
  • 이러한 경우를 처리하기 위해 Old 영역에서는 512 Byte의 덩어리(chunk)로 되어 있는 카드 테이블(card table)이 존재한다.
  • 카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시된다. Young 영역의 GC를 실행할 때, Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC 대상인지 식별한다.

카드 테이블 모습

<카드 테이블>

  • write barrier을 사용해 관리한다. write barrier은 Minor GC를 빠르게 할 수 있도록 하는 장치이다.
  • write barrier 때문에 약간의 오버헤드는 발생하지만 전반적인 GC 시간은 줄어든다.
이 문제를 해결하기 위해 실제로 store buffer에서 old-space에서 new-space로의 포인터 목록을 유지 관리합니다 . new-space의 객체에 대한 포인터가 old-space의 객체 필드에 기록될 때마다 저장 버퍼에 해당 필드의 위치를 ​​기록합니다. 이를 수행하기 위해 대부분의 저장 후에 이러한 포인터를 감지하고 기록 하는 write barrier이라는 약간의 코드를 실행합니다.

가비지 컬랙션: https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection

 

Young 영역의 구성

  • 객체가 가장 먼저 생성되는 Young 영역
  • 3개의 구성
    • Eden 영역
    • Survivor 영역(2개)
  • 각 영역의 처리 절차
    1. 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
    2. Eden 영역에서 GC가 한 번 발생한 후, 살아남은 객체는 Survivor 영역 중 하나로 이동한다.
    3. Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
    4. 하나의 Survivor 영역이 가득 차게 되면, 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득찬 Survivor 영역은 비운다.
    5. 이 과정을 반복하다 계속해서 살아남아 있는 객체는 Old 영역으로 복사된다.
  • Survivor 영역 중 하나는 반드시 비어있는 상태로 남아있어야 한다.
  • 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 시스템이 정상적인 상황이 아니다..!!

 

HotSpot JVM에서는 보다 빠른 메모리 할당을 위해 두 가지 기술을 사용한다.

  1. bump-the-pointer
  2. TLABs(Thread-Local Allocation Buffers

<bump-the-pointer>

  • Eden 영역에 할당된 마지막 객체를 추적한다.
  • 마지막 객체는 Eden 영역의 맨 위(top)에 있다. 그리고 그 다음에 생성되는 객체가 있으면, 해당 객체의 크기가 Eden 영역에 넣기 적당한지 확인한다.
    • 만약, 해당 객체의 크기가 적당하면 Eden에 넣고 top 위에 쌓이게 된다.
    • 따라서 새로운 객체를 생성할 때, 마지막에 추가된 객체만 점검하면 되므로 빠르게 메모리 할당이 이루어진다.

멀티 스레드 환경을 고려한다면?

Thread-Safe하기 위해, 여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 락(lock)이 발생할 수 박에 없고, lock-contention 때문에 성능이 떨어진다.

HotSpot JVM에서 이를 해결한 것이 TLABs이다.

<TLABs(Thread-Local Allocation Buffers>

  • 각각의 스레드가 각각의 몫에 해당하는 Eden 영역의 작은 덩어리를 가질 수 있도록 하는 것
  • 각 스레드는 자기가 갖고 있는 TLAB에만 접근할 수 있다.
  • bump-the-pointer을 사용하더라도 아무런 락 없이 메모리할당이 가능하다.

Promotion

⇒ Survivor 영역에 오래 살아남은 객체들이 특정 age에 도달하면 Old 영역으로 복사된다(== promotion)

Old 영역에 대한 GC

old 영역은 데이터가 가득 차면 GC를 실행하며, GC 방식에 따라 처리 절차가 달라진다.

GC 방신은 JDK7을 기준으로 5가지 방식이 있다.

  • Serial GC
  • Parallel GC
  • Parallel Old GC(Parallel Compacting GC)
  • Concurrent Mark & Sweep GC(이하 CMS)
  • G1(Garbage First) GC
  • 운영 서버에서 절대 사용하면 안되는 방식이 Serial GC
  • Serial GC는 데스크톱의 CPU 코어가 하나만 있을 때 사용한다. 애플리케이션 성능이 많이 떨어진다.

 

  • mark: 접근 가능한 객체에 대해 Mark해 표시
  • sweep: Mark되지 않은 객체들을 제거
  • compact: sweep 과정에 의해 삭제되면 메모리 단편화가 발생해 Compact를 통해 빈자리를 채워준다.

 

접근 가능한 객체 판단 과정

  • JVM 메모리 Stack 영역에 존재하는 참조변수
  • Method Area의 static 데이터
  • JNI에 의해 생성된 객체들

1️⃣ Serial GC

Old 영역의 GC는 mark-sweep-compact라는 알고리즘을 사용한다.

  • 적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식(싱글 스레드)
  • 다른 GC에 비해 stop-the-world 시간이 길다.
  1. Old 영역에 살아있는 객체를 식별(Mark)한다.
  2. heap의 앞 부분부터 확인하며 살아있는 것(Mark된 것)만 남긴다.(Sweep)
  3. 마지막 단계에서는 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다.(Compaction)

 

2️⃣ Parallel GC

Serial GC와 Parallel GC의 차이

Serial GC와 기본적인 알고리즘은 같다.

그러나, Serial GC는 GC를 처리하는 스레드가 하나이고, Parallel GC는 GC를 처리하는 스레드가 여러 개이다.

👍 보다 빠르게 객체를 처리할 수 있다.

  • Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하다.
  • Thoughput GC라도고 불린다.
  • Java 8의 기본 GC
  • Young 영역에서만 멀티스레드를 사용할 수 있다.

 

3️⃣ Parallel Old GC

  • JDK 5-6부터 제공한 GC 방식
  • Old 영역에도 멀티스레드를 사용한다.

Old 영역에서 Mark-Summary-Compaction 알고리즘을 사용한다.

  1. Old 영역에 살아있는 객체를 식별(Mark)한다.
  2. 앞서 GC를 수행한 영역에 대해서 별도로 살아있는 객체를 식별한다.(Summary)
  3. 마지막 단계에서는 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다.(Compaction)
    • 좀 더 복잡한 단계를 거친다.

 

4️⃣ CMS GC

Serial GC와 CMS GC의 차이

  1. 초기 Initial Mark 단계에서 클래스 로더에 가장 가까운 객체(GC Root가 참조하는 객체) 중 살아있는 객체만 찾는 것으로 끝낸다. 멈추는 시간이 매우 짧다.
  2. Concurrent Mark 단계에서 방금 살아있다고 확인한 객체에서 참조하는 객체들을 따라가 모두 Mark한다.
    1. 다른 스레드가 실행 중인 상태에서 동시에 진행된다.
  3. Remark 단계에서 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.(확정하는 단계)
  4. Concurrent Sweep 단계에서 쓰레기를 정리하는 작업을 실행한다.
    1. 다른 스레드가 실행 중인 상황에서 동시에 진행한다.
  • 👍 stop-the-world 시간이 매우 짧다.
  • 모든 애플리케이션의 응답 속도가 매우 중요한 순간에 사용한다. Low Latency GC라고도 부른다.

<👎 CMS의 단점>

  • 다른 GC보다 메모리와 CPU를 많이 사용한다.
  • Compaction 단계가 기본적으로 제공되지 않는다.
  • 조각난 메모리가 많아 Compaction 작업이 실행되면 다른 GC 방식보다 stop-the-world 시간이 길어질 수 있다.

 

5️⃣ G1 GC

이전의 GC 방식과 완전 다르다.

G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다.

해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다.

즉, 앞선 Young 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC라고 생각하면 된다.

G1 GC는 CMS GC를 대체하기 위해 만들어졌다.

 

  • Heap을 Region이라는 일정한 부분으로 나눠 메모리를 관리한다.
  • 전체 Heap을 탐색하지 않고 부분적으로 Region 단위로 탐색해 각각의 Region에만 GC를 발생시킨다.
  • 👍 가장 큰 장점은 성능
  • 어떤 GC 보다 빠르다.
  • JDK 6에서는 G1 GC를 earlly access라고 부르며 시험삼아 사용할 수 있도록만 하고 JDK 7에서 제공하고 있다.
  • 👎 JDK 7를 실서비스에 사용하려면 아직 많은 검증 기간이 필요하다.

 

 

이 글을 참고했습니다 - https://d2.naver.com/helloworld/1329

 

'스터디' 카테고리의 다른 글

DTO, VO, Entity 차이  (0) 2022.10.22