본문 바로가기
JAVA 정리노트

JAVA [GC, Garbage Collection] 정리본

by Dodledd 2024. 2. 3.

가비지 컬렉션이란?

유효하지 않은 메모리들을 처리해주는 프로세스라고 보면 된다.

그럼 유효하지 않은 메모리는 무엇일까?

밑에 코드를 보면

public class ad {
	public static void main(String[] args) {
		num number = new num();
		
		number.setNum(10);
		
		System.out.println(number.getNum());
		
		number=null;

	}
}

숫자 하나를 저장하는 클래스를 참조해서 만든 객체 number에 10의 값을 줬지만 마지막 줄 number에 null을 대입해서

객체를 가리키는 참조변수의 값을 없애버려서 number객체의 10 값은 아무도 찾아주지않는 외딴섬이 됐다.

바로 이때가 유효하지 않은 메모리로써 가비지 컬렉터의 먹이가 된다.

위에 이야기를 참고해서,

정확하게 이야기 해보면 heap 영역에 있는 동적 메모리 중 객체에 레퍼런스[각주:1]가 있다면 Reachable[각주:2]로 구분되고, 객체에 유효한 레퍼런스가 없다면 Unreachable[각주:3]로 구분해버리고 수거해버린다.  [각주:4]

 

그럼 GC [각주:5] 가 어떤 기준으로 제거 대상을 분별하는 능력을 갖췄는지 알았다.

이제 어떤 식으로 제거하며 동작하는지를 알아보자.

 

GC의 동작방식

 

GC가 청소하는 방식은 크게 3가지로 나뉜다.

1. Mark                            ==          1. 목표조준

Root Space [각주:6] 부터 하나하나 참조된 객체들을 찾아가서 누가 Unreachable 상태인가 식별한다.

 

2. Sweep                         ==          2. 격발(제거)

Unreachable 상태인 친구들의 식별이 끝났다면 메모리를 해체해버린다. (heap 영역에서 삭제한다)

 

3. Compact                     ==          3.  앞에 빈공간 없게 당겨

heap 영역에서 삭제됐으니 남은 빈공간이 있을텐데 이것을 압축하는 작업이다.

어디로? heap영역의 시작주소로

 

밑에 그림을 보면 한 번에 이해가 갈 것이다.

출처: https://inpa.tistory.com/entry/JAVA-☕-가비지-컬렉션GC-동작-원리-알고리즘-💯-총정리#thankYou [Inpa Dev 👨‍💻:티스토리]리 [Inpa Dev 👨‍💻:티스토리] 의 한 부분.

 

여기까지가 큰 틀에서의 GC를 알아보았다.

하지만, "그래서 GC가 정확히 언제 동작하는데? " 라는 물음이 또 들 수 있다.

이것을 알기 위해서는 Heap의 메모리 구조를 좀 더 Deep하게 살펴봐야한다.

그리고 GC는 두 가지 종류가 있는데 minor gc와 major gc가 있다는 것만 알고 보면된다.

 

  --------------------------Young generation---------------------       ||     ---------------------------Old generation----------------------------

Eden Survival 0 Survival 1 Old generation

                                          ----->                ------>               ------>

크게 젊은세대 노인세대로 나뉘는데 young 구간에서는 minor gc가 동작하고

old 구간에서는 major gc가 동작한다.

그럼 각 구간을 살펴보기 전에 알아야 할 것 두 가지.

첫 번째 : Survival 0 나 Survival 1 둘 중 한 곳은 무조건 비어있어야함.

두 번째 : age bit가 있다. (우리가 for문 돌릴 때 count 세듯 GC도 필요한가보다)

 

Eden : 새로운 객체가 만들어지는 곳. ( 우리가 new 를 사용해서 객체를 만들 때 !) ,  minor gc 동작

 

Survival 0 : Eden 공간이 꽉 차버려서 넘어오는 곳, 이 때 Unreachable 인 녀석들은 삭제, reachable 인 녀석들은 생존해서 age bit를 하나 얻는다.     (age bit == 1) , 

 

Survival 1 : 마찬가지로 Survival 0 공간이 꽉 차서 넘어오는 곳, 이 때 Unreachable 인 녀석들은 삭제, reachable 인 녀석들은 생존해서 age bit를 하나 얻는다.     (age bit == 2) ,

 

Old generation : Survival 1에서 최대 age bit를 가진 녀석들이 넘어오는 곳. (최대 age bit는 자유롭게 설정이 가능하다고 한다.) 그리고 Old generation에 있는 데이터는 Promotion 이라고 한다.

 

마지막으로 Old generation이 꽉 차게 되면 major gc가 동작한다.

 

그럼 왜 이렇게 나눠놨을까?

그건 객체의 수명이 대부분 짧다는 것을 GC 만든 분들이 알아버렸기 때문이다.

보통 객체를 만들고 한 번 참조하고 잘쓰지 않는다는 사실을 생각해보면 알 수 있다.

 

 

GC를 사용하는 목적은 무엇인가...

개발자가 메모리를 신경쓰지 않고 오로지 개발에만 집중할 수 있도록 만들어 진 것이다.

하지만 GC를 사용하는 것도 공짜는 아니다.

메모리를 정리하기위해 메모리를 사용해야하는 모순이지만 얻는 이득이 훨씬 큰 것 같다.

 

그럼 GC를 사용해서 얻는 이득은 알겠지만 어떤 손해를 감수해야 하는가?

위에서 본 Old generation 구역이 꽉 차서 major gc가 실행된다면, 모든 Thread가 멈추고 Mark and Sweep 동작을 들어가기 때문에 cpu가 부하를 받아 멈추거나 버벅거림 현상이 생길 수 있다.

이 현상을 stop - the - world 라고 한다.

시대는 변했고 하드웨어도 발전한 만큼 GC의 알고리즘도 발전했다.

GC의 여러가지 알고리즘

*CPU의 코어나 쓰레드가 뭔지 모르는 사람을 위해 비유해서 설명하자면

CPU는 부품이름 노예의 이름이고

코어의 개수 = 내가 고용할 노예의 수

쓰레드 = 내가 고용한 노예의 손 개수

Serical GC

  • 자바의 옛날버젼 Java SE, 5,6 에서 사용되는 default GC
  • 아주 옛날 CPU의 코어가 1개일 때 사용했던 GC이다. [각주:7]
  • GC를 처리하는 스레드가 1개(싱글 쓰레드)이다. 그렇기에 stop - the - world 가 길다.
  • 실무에서 사용하는 경우는 거의 없다, 예외로 cpu가 하나면 이것만 사용할 수 있다.

 Parallel GC

  • 필자가 자주 사용하는 Java 8의 default GC 이다
  • Old 영역은 Serical과 마찬가지로 싱글 쓰레드이다.
  • Serical이랑 다른 점은 young 영역의 GC 인 minor gc가 동작될 때 멀티 쓰레드로 돌아간다.
  • 멀티 쓰레드인 만큼 속도가 빨라져서 stop - the - world의 시간이 짧아진다.

Parallel Old GC

  • 위의 Parallel GC의 단점인 Old 영역에서 싱글 쓰레드로 돌아가는 단점을 극복한 버젼이다
  • young도 old영역도 모두 멀티 쓰레드로 GC 사용한다.
  • 새로운 GC의 청소방식 Mark - Summary - Compact 방식을 사용한다.

CMS GC

  • 애플리케이션의 쓰레드 + GC의 쓰레드가 동시에 돌아가서 stop - the - world 를 최대한 줄여보겠어 !! 하고 나온 알고리즘이지만 다른 GC 대비 CPU 사용량이 높고 메모리 파편(단편) 화  [각주:8] 문제가 있어서 결국 Java9에서는 deprecated [각주:9] 되고 Java 14에서 퇴출당했다.

G1 GC

  • CMS GC가 문제가 되면서 나온 Java9+ 버전의 기본 GC이다
  • 4GB 이상의 힙 메모리가 필요하고 stop - the - world 가 0.5초 정도 걸린다.
  • 새로운 Region 개념이 도입됐다. 위의 GC 알고리즘에서는 물리적으로 고정된 young 영역 old 영역을 나눴지만 G1 GC는 힙 영역을 Region이라는 체스판 방식으로 분할하여 상황에 따라 Eden, Survivor, Old 영억을 동적으로 부여한다.
  • 메모리가 많이 차 있는 영역을 인식하여 우선적으로 GC하고 체스판 한칸 한칸을 영역별로 탐색, GC 가 일어난다고 보면 된다.
  • 위의 다른 GC 는 Eden -> Survivor -> Old 순차적으로 이동했다면 G1 GC는 이러한 틀에서 벗어나 재할당을 시켜버린다. ex) Survivor에 있는 객체가 Eden에 있는게 더 좋겠는걸? 하면 바로 재할당해서 Eden에 놓는다.

Shenandoah GC

  • Java 12에서 릴리즈 된 버전이다.
  • 레드 햇에서 개발했고 크게 한 번 GC 하는 것보다 작게 여러번 하는 GC 가 더 좋다라는 방식이다.
  • CMS의 단편화, G1의 pause[각주:10] 이슈를 해결했고 일정한 pause의 시간이 소요된다.

ZGC

  • Java 15에서 릴리즈 된 버젼이다.
  • 대량의 메모리 (~16TB)를 low - latency [각주:11]로 처리하기 위해 만들어졌다.
  • G1이 사용하는 체스판의 조금 고급버젼인 Zpage라는 영역을 사용하며 이 공간을 2^의 배수로 동적 할당이 가능하다.
  • 압도적인 장점이 있는데 아무리 힙 크기가 증가하더라도 stop - the - world 의 시간이 10ms[각주:12]를 넘지 않는다는 장점이 있다.

 

마지막 필자의 주저리...

개인적인 생각이지만 GC는 실시간으로 동작하고 고사양 하드웨어가 들어갈 수 없는 곳에서는 치명적인 단점이 되지 않을까? 예를들면 한 발에 전투기 미사일같은 곳에는 어떤 소프트웨어가 들어가있을까 하고 검색해보니 한 발에 5억이다.

돈이면 해결이 되나보다.

마지막으로 항상 공부할 때 마다 드는 생각이지만 영화 (이미테이션 게임), 폰 노이만의 관련 서적을 보고 난 후의 기분이 계속해서 든다.

세상에 천재는 많다..

  1. 참조값 [본문으로]
  2. 객체가 참조 된 상태 [본문으로]
  3. 객체가 참조되지 않은 상태 [본문으로]
  4.   [본문으로]
  5. Garbage Collection이하 GC [본문으로]
  6. JVM에서 GC가 동작하는 공간( Stack의 로컬변수, Method Area의 Static 변수 [본문으로]
  7. 지금은 몰랐지 6코어 8코어가 가정용 PC로 사용 될지.. [본문으로]
  8. 메모리가 할당되고 해제되는 작업이 반복되면서 메모리의 공간이 작은 공간으로 나뉘어져 사용가능한 메모리가 충분히 존재하지만 할당이 불가능한 상태. [본문으로]
  9.   명령 혹은 문장이 나중에는 쓰이지 않게 될 것이니 주의해라. 라고 자바가 알려주는 것 [본문으로]
  10. stop the world의 다른 말 [본문으로]
  11. 저지연 [본문으로]
  12. 0.01초 [본문으로]