[Java] Garbage Collection 기초 공략하기

haero_kim·2021년 9월 22일
42

CS 뿌셔먹기

목록 보기
2/12
post-thumbnail

C/C++ 프로그래밍을 해본 사람들이라면 알테지만, 객체를 생성한 이후 더이상 사용하지 않는 객체에 대해서는 free() 등을 활용해 메모리를 꼭 해제해줘야 했다. 만약 메모리를 해제하지 않는다면 메모리 릭이 발생하고 말 것이기 때문이다.

그러나 자바에서는 JVM 이 구성된 JRE (Java Runtime Environment) 가 제공되고, JRE 의 구성 요소 중 하나인 Garbage Collection (이하 GC) 가 위 과정을 알아서 해준다. 매우 편리한 것이다.

GC 에 대해 살펴보기 전에, stop-the-world 라는 용어를 알아둬야 한다.

stop-the-world 란?

GC 를 실행하기 위해 JVM 애플리케이션 실행을 멈추는 것이다. stop-the-world 가 발생하면 GC 를 실행하는 쓰레드를 제외한 나머지 모든 쓰레드가 작업을 멈춘다. GC 작업이 완료되면, 중단됐던 작업이 재개된다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world 가 발생하기 때문에, GC 성능 최적화 (튜닝) 이라함은 결국 이 stop-the-world 타임을 줄이는 것을 의미한다.

규모있는 자바 애플리케이션을 효율적으로 개발하기 위해서는, 이 GC 에 대해서 심도있게 이해할 필요가 있다. 만약 JVM 애플리케이션을 개발하는데 아직 GC 의 동작원리에 관해 정확히 잘 몰랐던 사람들에게 이 포스팅을 바친다.

위와 같이 납치당하기 싫으면 (???) GC 를 몰라도 된다.

Garbage Collection

C/C++ 과 다르게 자바에서는 개발자가 명시적으로 객체를 메모리 상에서 직접 해제할 필요가 전혀 없다. 다시 정리하자면 사용하지 않는 객체를 자동으로 메모리에서 제거하는 작업을 GC 라고 부른다. 이는 JVM 에서 수행되는 작업이다. 용어 자체도 쓰레기 수집 정도로 해석할 수 있는데, 이름조차 찰떡이지 않은가?

다들 알다시피(?) JVM 의 메모리는 Class, Stack, Heap, Native Method, PC 이렇게 총 5가지로 이루어지는데, GC 는 이 중 Heap 메모리를 관장한다.

Garbage Collection 대상

  • 값이 Null 인 객체
  • 특정 블럭 실행 종료 시 블럭 내에서 생성된 객체
  • 부모 객체가 Null 일 때, 해당 객체가 포함하고 있는 자식 객체

그럼, 어떻게 사용하지 않는 메모리를 알아서 해제하는지 살펴보자.

일반적인 GC 메모리 해제 과정

1. Marking

우선, 메모리가 사용되는 지 안 되는지를 구분한다. 위 사진에서는 참조되지 않고 있는 객체를 주황색으로 마킹한 것이다. 모든 객체를 하나씩 탐색하며 마킹하기 때문에 오랜 시간을 소모하게 된다.

2. Normal Deletion

아까 마킹을 했으니, 마킹한 객체 (참조되지 않고 있는 객체) 를 제거하고 메모리를 반환한다. Memeory Allocator 는 반환되어 비어진 곳의 주소를 저장해뒀다가, 새로운 객체가 선언되면 이 곳에 할당되도록 한다.

그런데, 이렇게 하면 메모리가 단편화되어 비효율적으로 사용될지도 모른다. 따라서 흩어진 객체들의 영역을 한 곳으로 모으는 과정이 필요하다.

3. Compacting

참조되는 객체들을 한 곳으로 집약시켜, 남은 공간을 극대화함으로써 새로운 메모리 할당 시 훨씬 더 쉽고 빠르게 진행할 수 있다.

지금까지의 과정을 Mark & Compact 라고 한다. 하지만 모든 객체를 탐색해가며 일일히 참조되지 않고 있는 객체를 마킹하고 메모리 해제하고 다시 메모리 압축하고 난리도 아니다. 상당히 비효율적인 알고리즘이다.

Weak Generational Hypothesis

Y 축은 할당된 바이트의 수, X 축은 바이트가 할당될 때의 시간이다. 시간이 가면 갈 수록 적은 객체가 남게 된다. 위 그래프에 기반하여 Weak Generational Hypothesis 라는 가정이 등장하게 되었다.

  • 대부분 객체는 금방 접근 불가능 상태 (Unreachable) 가 된다.
  • 오래된 객체에서 생성된지 별로 안 된 객체로의 참조는 아주 적게 존재한다.

신규로 생성한 객체의 대부분은 머지않아 사용하지 않는 상태가 되고, 오래된 객체에서 신규 객체로의 참조는 매우 적게 일어난다는 가정이 바로 Weak Generational Hypothesis 이다.

이러한 가정하에 Young 영역과 Old 영역으로 메모리를 분할하여, 새롭게 생성되는 객체는 Young 영역에 보관하고, 점차 나이를 들게 한 다음 어느정도 나이를 먹은 객체는 Old 영역에 보관하는 알고리즘을 고안해냈다.

이것을 Generational Gabage Collection 이라고 한다.

Generational Gabage Collection

Young Generation 영역

새롭게 생성한 객체들은 대부분 이곳에 자리잡게 된다. Weak Generational Hypothesis 에 의거하면, 대부분 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 이 영역에서 생성되었다가 사라지게 된다. 해당 영역에서 객체가 메모리 해제되는 경우를 Minor GC 가 발생했다고 말한다.

Old Generation 영역

금방 접근 불가능 상태가 된 녀석들과 달리, 꽤 오랫동안 메모리에 남아있던 객체들이 이 영역으로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 GC 는 Young 영역에 비해 적게 발생하게 된다. 해당 영역에서 객체가 메모리 해제되는 경우 Major GC 가 발생했다고 말한다.

Permanent 영역

Method Area 라고 부르기도 하는 이 영역은, JVM 이 클래스들과 메소드들을 설명하기 위해 필요한 다양한 메타데이터들을 포함하고 있다. JDK8 부터는 Metapsace 로 교체된다.

Generational Gabage Collection 과정

1. 새로운 객체가 생성되면 Young 영역의 Eden Space 에 할당

에덴은 기독교 세계관 상 최초의 인간인 아담과 이브가 지냈던 낙원이다. 이처럼, 최초로 생성된 객체들을 위한 공간이라고 하여 Eden Space 라는 이름이 붙은 것 같다.


2. Eden Space 가 가득차면, Minor GC 수행

3. 참조되는 객체들은 첫 번째 Survivor 영역 (S0 영역) 으로 이동하고, 참조되고 있지 않는 (접근 불가능한) 객체들은 Eden Space 가 클리어 될 때 같이 반환

4. 다음에 일어나는 Minor GC 때, Eden Space 에선 똑같은 일이 일어남

비 참조 객체는 사라지고, 참조 객체는 마찬가지로 Survivor 영역으로 이동한다. 하지만 이번에는 두 번째 영역인 S1 영역으로 이동한다.

게다가 최근 Minor GC 에서 S0 영역으로 이동된 객체들도 Age 가 증가하고, S1 영역으로 이동하게 된다. 모든 객체들이 S1 영역으로 이동하게 되면, Eden Space 와 S0 영역은 모두 클리어된다.

이제 우리는 각기 다른 Age 를 가진 객체들을 Survivor 영역에 가지게 된다.


5. 다음 Minor GC 때 역시 같은 과정이 반복

하지만 이번 과정에선 Survivor 영역이 서로 스위치된다. 참조되는 객체들은 S0 으로 이동하고, 살아남은 객체들은 Age 가 증가한다. 그리고 Eden Space 와 S1 영역은 클리어 된다.


6. Minor GC 이후 일정한 Age 임계값을 넘은 Age 를 가진 객체들이 Old Generation 으로 프로모션됨 (아래의 경우 Age Threshold : 8)
어린 놈들과 세대 차이를 느껴서 도망 간 것이다. ㅈㅅ

7. Minor GC 가 계속될수록 틀딱 객체들이 Old Generation 영역으로 이동

지금까지 과정 한눈에 보기

  1. Eden 영역에 최초로 객체 생성
  2. Survivor 영역을 통해 오래 살아남은 객체가 Old 영역으로 이동

Old Generation 영역도 마찬가지로 메모리가 가득차면 Major GC 가 시행된다. Major GC 는 다양한 방식이 존재하는데, 추후 다양한 방법들 중 안드로이드가 사용하는 'CMS' 방식에 대해 포스팅해보려 한다.


이번 포스팅에선 GC 의 기본적인 원리에 대하여 알아보았다. 심도있는 내용이지만 간략히 개념만 훑어보았다. 따라서 JVM 개발자들은 다양한 자료를 찾아보며 GC 에 대해 깊게 삽질해보는 것이 분명 좋은 양분이 될 것이다.

참조자료

https://d2.naver.com/helloworld/1329
https://gyoogle.dev/blog/computer-language/Java/Garbage Collection.html

profile
어려울수록 기본에 미치고 열광하라

8개의 댓글

comment-user-thumbnail
2021년 9월 24일

다소 난해한 지식을 항상 유쾌하게 풀어내시는 능력이 대단하다고 생각합니다.
이해 여부와 관계 없이 늘 재밌게 보고 있어요. 좋은 글 감사합니다 :)

1개의 답글
comment-user-thumbnail
2021년 9월 25일

항상 유쾌한 썸네일에 이끌려서 들어오는 게시글이네요 ㅋㅋ 항상 잘보고있습니다 :)

1개의 답글
comment-user-thumbnail
2021년 9월 27일

많은 도움이 되었읍니다,,,,,,,,,,,,,,,,,,,,,,,,,,

1개의 답글
comment-user-thumbnail
2021년 9월 27일

It's nice!

1개의 답글