Post

자바의 가비지 컬렉터

개론

대부분의 고수준 프로그래밍 언어들은 개발자가 메모리를 직접 해제하지 않는다. 별도의 프로그램이 동적으로 할당된 메모리의 여유분을 관리하는데 이를 가비지 컬렉터라고 한다.

그에 반해 c, c++과 같이 기계에 조금 더 가까운 언어들은 메모리 관리에 대한 책임이 전적으로 개발자에게 있다.

그렇다면 가비지 컬렉터는 어떤 방식으로 작동하는지 자바의 사례를 통해 알아보자.

가비지 컬렉션

가비지 컬렉션이란

가비지 컬렉션은 힙 메모리에서 사용 중인 개체와 사용되지 않는 개체를 식별하고, 사용되지 않는 개체를 삭제하는 프로세스이다.

여기서 사용 중인 개체의 의미는 현재 어플리케이션이 해당 개체에 대한 포인터를 유지하고 있음을 의미, 즉 참조를 하고 있다는 뜻이다.

1
2
3
4
5
pubilc void main(){
    A a = new A(); // 객체 할당
    a = null; // 참조관계 끊어짐
}

이를 자바 관점에서 서술하자면 스택에 변수가 할당되고, 힙에 객체가 할당 되었을 때 변수와 참조하고 있는 힙의 객체가 연결이 끊어진 상황을 의미한다.

이러한 개체는 더 이상 프로그램에 필요하지 않기 때문에 메모리를 회수해야 할 대상이다.

C와 같은 프로그래밍 언어에서 메모리 할당 및 할당 해제는 수동으로 이루어지지만, 자바에서는 메모리 할당 해제 프로세스가 자동으로 처리되며, 이러한 작업을 가비지 컬렉션이라 부른다.

진행 과정

1. 마킹

Slide3

먼저 사용 중인 메모리와 사용하지 않는 메모리를 식별한다. 위의 그림에서 현재 참조 중인 객체는 파란색이고, 참조가 끊긴 개체는 주황색으로 표시되어 있다.

이러한 마킹을 하기 위해 메모리의 모든 개체를 스캔해야 하며, 메모리 가용량에 따라 시간이 오래 걸릴 수 있다.

2. 일반 삭제

Slide1b

참조가 끊긴 개체를 삭제하고, 여유 공간을 확보한다. 그리고 어느 부분에 새 개채를 할당할 수 있는지 여유 공간에 대한 정보를 획득한다.

2a. 압축을 통한 삭제

Slide4

성능 향상을 위해서는 참조가 끊긴 개체를 삭제할 뿐만 아니라 나머지 개체들을 연속되게 재배치 하는 것도 가능하다.

이렇게 하면 메모리 파편화를 방지하여 새 메모리 할당 퍼포먼스가 좋아질 수 있다.

세대(Generation)

세대별 가비지 수집의 필요성

위에서 기술한 방식으로 JVM의 모든 개체를 스캔, 압축하는 것은 비효율적일 수 있다. 어플리케이션이 실행되면서 더 많은 개체가 할당될 것이고, 스캔해야 하는 목록이 늘어남에 따라 가비지 수집 시간은 비례하게 늘어난다.

그러나 점차 개발을 하며 대부분의 개체는 금방 할당되었다가 해제되는, 즉 수명이 짧은 개체의 수가 훨씬 많다는 것이 경험적으로 분석되었다.

ObjectLifetime

Y축은 할당된 개체의 갯수이며 X축은 시간이 지남에 따라 남아있는 개체의 숫자이다.

(근데 이 차트는 뭘 근거로 만들어 졌는지는 나도 모르겠다… 오라클 내부 데이터인가?)

위의 차트와 같이 시간이 지나면서 살아남는 개체의 수는 급격하게 적어지고, 대부분은 얼마 지나지 않아 참조가 끊기는, 소멸되는 것을 볼 수 있다.

세대별 특징

Slide5

JVM은 그래서 힙에 할당된 개체들을 다음과 같은 4개의 세대로 크게 구분한다.

그런데 자바 버전이 업데이트 되면서 위와 같은 구분이 약간은 변동되었고, 지금은 다음과 같은 구조를 가지고 있다.

img

미리 변경된 영역에 대해서만 설명을 하자면, 메타스페이스는 클래스와 메소드의 메타데이터들이 저장되는 영역이다. 클래스가 로딩될 때 메타스페이스에 클래스 정보가 저장되는데 대규모의 프로그램에서는 당연히 클래스 정보도 많을 것이고, 메타스페이스의 사용량 또한 늘어나게 된다.

기존의 Perm 영역은 자바 힙 영역에 포함되었기 때문에 메모리 확보에 비교적 제약이 있었으나, 이제는 OS 레벨에서 관리되는 Native 메모리 영역으로 옮겨졌기 때문에 비교적 여유로운 상한선 확보가 가능해졌다.

다시 본론으로 돌아와서 각 세대별에 대한 설명을 마저 하자면, Young Generation은 모든 개체가 할당되는 시작점이다. Young Generation이 가득 차게 되면 마이너 가비지 컬렉션이 발생하게 된다. 가장 가비지 수집이 활발한 영역이며, 이곳에서 생존하면 Old Generation으로 이동하게 된다.

Old Generation으로 넘어온 개체도 점점 쌓이게 되면 가비지 컬렉션이 발생하는데, 여기서 일어나는 가비지 컬렉션을 메이저 가비지 컬렉션이라고 한다.

마이너, 메이저 가비지 컬렉션 둘다 실행되고 완료되기 전까지 Stop The World 이벤트, 즉 모든 애플리케이션의 스레드가 정지하게 된다. 이러한 방식의 GC를 극복하기 위해 다양한 방식의 가비지 컬렉션이 개발되었는데(Parallel GC, CMS GC, G1 GC, Z GC) 여기 쓰기엔 글이 너무 길어지니 다른 게시글로 정리하도록 하겠다.

세대별 가비지 컬렉션

1. eden 할당

Slide13

제일 먼저 eden 영역에 새로운 객체들이 할당된다.

2. 마이너 GC

Slide14

그리고 eden 공간이 가득 차게 된다면 마이너 GC가 발생한다.

3. S0 이동

Slide6

여기서 현재도 참조 중, 사용 중인 객체는 s0 으로 이동된다. 참조하고 있지 않는 객체는 마이너 GC와 함께 삭제된다.

4. S1 이동

Slide8

위의 세 과정을 반복한 후, S0에서 또 다시 생존한 객체는 S1으로 이동하게 된다.

5. Old Gen 이동

Slide10

마이너 GC가 반복되며 생존한 객체들은 더 상위 단계인 Old Gen으로 이동하며, 이 영역이 가득차게 되면 메이저 GC가 발생한다.

참고

  • Oracle, Java Garbage Collection Basics, https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

  • bebeside77, Java Metaspace에 대해서, https://sheerheart.tistory.com/entry/Java-Metaspace%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

  • BE Developer, Java 8에서 JVM의 변화 : PermGen이 사라지고 Metaspace가 등장하다., https://goodgid.github.io/Java-8-JVM-Metaspace/

This post is licensed under CC BY 4.0 by the author.

© . Some rights reserved.

Using the Jekyll theme Chirpy