[java] GC 가비지 컬렉션

CodeKong의 기술 블로그·2024년 3월 27일
4

JAVA

목록 보기
3/5
post-thumbnail

Garbage Collection

"자바의 메모리 관리 방법"
동적으로 할당한 메모리 중에서 더 이상 사용하지 않는 부분을 자동으로 찾아서 해제하는 프로세스이다.

가비지 컬렉션은 왜 필요할까?

GC의 필요성

for (int i = 0; i < 1000; i++) {
            String garbage = new String("Garbage " + i);
            // 각 반복마다 새로운 문자열 객체를 생성합니다.
            // 이전에 생성된 문자열 객체들은 더 이상 참조되지 않으므로 가비지가 됩니다.
        }


다음 코드에서는 매번 String 객체를 생성하여 주소 값을 garbage에 매핑할 것이다.

이렇다면 매핑이 끊어진 객체들은 데이터를 저장하기 위해 메모리를 할당받지만
할당받았던 메모리 영역을 해제하지 않으면 해당 메모리는 다른 용도로 사용할 수 없게되고 자원은 줄어들게 된다.

이때 GC의 주요 작업이 영역에서 더 이상 사용되지 않는 객체들을 자동으로 식별하고 회수하는 것이다.

이로써 프로그램이 사용하는 메모리 영역을 효율적으로 관리하고, 메모리 누수를 방지하는데 도움을 준다.


그렇다면, GC가 어떻게 동작하는 좀 더 자세히 알아보자

GC의 작동 이론

GC의 기본 원리는 "도달 가능성(Reachability)"에 기반한다.

메모리 영역이 프로그램의 루트 집합(Root Set)에서부터 접근 가능한 경로가 존재한다면, 그 메모리는 여전히 유효(도달 가능)한 것으로 간주한다.

루트 집합은 전역 변수, 활성 스택 프레임의 지역 변수와 매개변수 등 프로그램 실행에 필수적인 참조들의 집합을 의미한다.

이해가 어렵다면 아래 그림을 보자

Book book1 = new Book("Java Programming");
Book book2 = book1;

Person person = new Person("Person1");
person = new Person("Person2");

이 코드를 간단하게 도식화 한다면

이렇게 요약할 수 있다.

이때 Person1에 해당하는 객체는 루트 집합에서 접근할 수 없으므로  Unreachable하다.

이런 Unreachable한 객체들을 어떤 알고리즘으로 삭제해줄까?


GC의 작동 원리

GC는 "Weak Generational Hypothesis"(약한 세대 가설)에 기인한다.

Weak Generational Hypothesis

대부분의 객체는 생성된 직후에 금방 가비지가 되는 경향이 있다는 가설이다.

이에 기반하여, JVM은 객체를 세대별로 관리한다.

Young Generation: 새로 생성된 객체들이 이곳에 배치된다.
Old Generation: Young Generation에서 살아남은 객체들이 이동하는 곳이다.
이 영역의 객체는 비교적 오래 살아남는 경향이 있다.

그렇다면 각 세대별로 적용되는 알고리즘을 알아보자


Young Generation

Young Generation은 새로 생성된 객체들이 배치되는 곳인 만큼, 빈번한 가비지 컬렉션이 발생한다.

때문에 Young Generation을 효율적인 메모리 관리를 위해 세부적으로 세 가지 영역으로 다시 나눈다.

Eden 영역은 새로 생성된 대부분의 객체가 할당되는 곳이다.
프로그램이 객체를 생성할 때, JVM은 먼저 이 Eden 영역에 객체를 배치한다.

이때 프로그램 실행 과정에서 Eden 영역이 가득 차게 되면, 가비지 컬렉션이 발생하고, 이를 Minor GC라고 한다.

Survivor 영역들은 Minor GC 과정에서 살아남은 객체들이 임시로 저장되는 곳이다. Survivor 영역은 Survivor 0과 Survivor 1 두 개가 있으며, 이들은 서로 역할을 교대합니다.

그림으로 순서를 알아보자


1. Eden 영역에 메모리가 가득차게 되면 Minor GC가 실행된다.

2. Mark-Sweep 알고리즘을 통해 먼저, 루트 집합에서부터 도달 가능한 객체를 "표시"한다.

3. 도달 가능한 객체를 Survivor0 영역으로 옮긴다.

  1. 도달 가능하지 않은 객체를 메모리에서 제거(sweep)하고, 살아남은 객체들은 age가 1 증가한다.


5. Eden 영역에 메모리가 가득차게 되면 Minor GC가 다시 실행된다.

6. 도달 가능한 객체들을 다시 "표시"한다.

7. 도달 가능한 객체를 Survivor1 영역으로 옮기고 age가 1증가한다.

이후 age 값이 미리 설정한 임계치에 도달했을 때는 Old Generation 영역으로 옮긴다(Promotion 발생)


Old Generation

Old Generation은 Young Generation의 GC만으로 메모리 문제를 해결할 수 없을 때 발생한다.

Young Generation에서 계속해서 Promotion(승격)이 일어나 Old Generation에 쌓이다가 가득차면 GC가 발생하고 이것을 Major GC또는 Full GC라고 한다.

이때는 전체 힙 영역에 대한 가비지 컬렉션을 수행하기 때문에, GC의 단점이 발생하게 되는데..


GC의 단점

"Stop-The-World"는 가비지 컬렉션(GC) 과정에서 JVM이 애플리케이션의 실행을 일시 중단시키는 현상을 의미한다.

Minor GC에서는 비교적 빠르게 GC가 완료되지만 Major GC는 전체 힙 영역을 대상이기 때문에 더 많은 시간이 걸린다.

GC 중에는 모든 애플리케이션 스레드가 중단되기 때문에, 사용자 요청에 대한 응답이 지연되거나, 실시간 처리가 필요한 작업에 부정적인 영향을 줄 수 있다.

해결 방법

Stop-The-World 최소화 방법으로는 4가지 정도가 있다.

간단하게 살펴보고 관련 포스팅은 별도로 할 예정이다

1. 적절한 GC 알고리즘 선택

G1 GC, ZGC 등 애플리케이션의 요구 사항과 메모리 사용 패턴에 맞는 GC 알고리즘을 선택한다.

2. 힙 사이즈 조정

JVM의 힙 사이즈를 애플리케이션의 메모리 요구량에 맞게 조정하여, GC 발생 빈도를 최적화한다.

3. GC 튜닝

Young Generation의 크기를 조정하거나, Full GC를 더 자주 실행하도록 설정하는 등 특정 GC 옵션을 조정하여, STW의 영향을 최소화할 수 있다.

4. 메모리 할당 최적화

애플리케이션 코드 내에서 객체 할당과 사용을 최적화하여, 가비지 생성을 줄인다.


결론

GC는 필수적이지만 관리가 필요한 기능이다. 때문에 적절한 GC 알고리즘과 설정이 중요하다. 메모리 사용 패턴을 이해하고 GC 로그를 모니터링해보고 효율적인 메모리 관리 전략을 개발해보는 등 더욱 많은 배울점이 남은거 같다.

0개의 댓글