Java의 GC 이해

Hyunjun Jang·2021년 10월 17일
0
post-custom-banner

GC란?

JAVA는 JVM을 통하여 구동된다. JAVA의 특징 중 하나로 메모리 관리를 개발자가 직접 명시적으로 수행하지 않고, JVM에서 자동으로 수행해 주며, 이와 같은 역할을 하는 프로세스를 Garbage Collector(GC)라 한다.

자바에서는 메모리를 GC라는 알고리즘을 통하여 관리(automatic memory management)하기 때문에, 개발자가 메모리를 처리하기 위한 로직을 만들 필요가 없고, 절대로 만들어서는 안된다.

Garbage Collection은 JAVA에서 쓰레기는 객체이다. 하나의 객체는 메모리를 점유하고, 필요하지 않으면 메모리에서 해제되어야 한다. 특정 시점 이후에는 새로운 객체 생성을 위한 충분한 메모리가 확보되지 않고 Out Of Memory Error로 인해 전체 프로그램이 비정상적으로 종료되어 서비스 장애로 까지 이어 질수 있다.

GC는 JVM의 백그라운드에서 자동적으로 메모리 관리를 수행하는 JVM의 기능이다. 이러한 GC는 JVM의 메모리 영역 중 Heap 메모리에서 이뤄지는데,
그 이유는 Java에서 새로운 객체가 할당될 때, Heap 메모리 영역에 할당 되기 때문이다.

JAVA의 GC는 다음과 같은 가설을 전재로 도입되었다

JAVA에서 GC의 도입의 전제 가설 (weak generational hypothesis)

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

아래와 같이 객체를 생성하고 해당 객체의 어떤 동작을 10,000번 수행하는 반복문에서 마지막 한번을 제외 한 9,999번의 객체들은 생성 후 바로 접근 불가능 상태가 된다. 이를 unreachable 상태라고 하며, GC가 발생하지 않으면 10,000개의 객체 현재 참조 되지 않지만 메모리를 점유 하고 있다고 볼 수 있다.

for (int i = 0; i < 10000; i++) {
    SampleObj obj = new SampleObj();
    obj.doTest(i);
}

또 아래와 같이 객체를 생성하고 다른 메소드나 클래스에 해당 객체를 전달 후 에는 다시 참조 하지 않는 경우가 대 부분이다. 이를 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생 한다라고 말 할 수 있다.

SampleObj obj = new SampleObj();
obj.setValue(1);
doTest2(obj);

GC의 원리

GC 작업을 하는 가비지 콜렉터(Garbage Collector)는 다음의 역할을 한다.

  1. 메모리 할당
  2. 사용 중인 메모리 인식
  3. 사용하지 않는 메모리인식

GC를 해도 더 이상 사용 가능한 메모리 영역이 없는데 계속 메모리를 할당하려고 하면, OutOfMemoryError가 발생하여 프로그램이 종료 될 수도 있다. 행(Hang) 즉, 서버가 요청을 처리 못하고 있는 상태가 된다.

JVM의 메모리는 크게 클래스 영역, 자바 스택, , 네이티브 메소드 스택의 4개 영역으로 나뉜다. 가비지 콜렉터에서는 힙 메모리를 다루게 됩니다. 즉 자바콜렉터가 인식하고 할당하는 자바 메모리 영역은 영역이다.

Young, Old, Perm 세 영역으로 나뉜다. 이 중 Perm(Permanent) 영역은 거의 사용되지 않는 영역으로서 클래스와 메소드 정보와 같이 자바 언어 레벨에서 사용되지 않는다.

Yong, Old 영역 그리고 Young 영역은 Eden영역과 두 개의 Survivor 영역으로 나뉜다.

우리가 고려해야 할 자바의 메모리 영역은 총 4개의 영역으로 나뉜다고 볼 수 있다. ( Young (1. Eden, 2. Survivor 1, 3. Survivor 2), Old (4. 메모리) )

일단 메모리에 객체가 생성되면, Eden 영역에 객체가 지정된다. Eden 영역에 데이터가 어느 정도 쌓이면, 이 영역에 있던 객체가 어디론가 옮겨지거나 삭제된다. 이 때 옮겨가는 위치가 survivor 영역이다. 두개의 Survivor 영역 사이에 우선 순위가 있는 것은 아니다. 하지만, 이 두 개의 영역 중 한 영역은 반드시 비어 있어야 한다. 그 비어있는 영역에 Eden 영역에 있던 객체가 할당된다.

Eden에서 survivor 둘 중 하나의 영역으로 할당 되고, 할당된 Survivor 영역이 차면, GC가 되면서 Eden 영역에 있는 객체와 꽉 찬 Survivor 영역에 있는 객체가 비어 있는 Survivor 영역으로 이동한다. 그러다가 더 큰 객체가 생성되거나, 더 이상 Young 영역에 공간이 남지 않으면 객체들은 Old 영역으로 이동하게 된다.

GC의 동작 방식

Young 영역과 Old 영역은 서로 다른 메모리 구조로 되어 있기 때문에, 세부적인 동작 방식은 다르다. 하지만 기본적으로 가비지 컬렉션이 실행된다고 하면 다음의 2가지 공통적인 단계를 따르게 된다.

  • Stop The World
    Stop The World는 가비지 컬렉션을 실행하기 위해 JVM이 애플리케이션의 실행을 멈추는 작업이다. GC가 실행될 때는 GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업이 중단되고, GC가 완료되면 작업이 재개된다. 당연히 모든 쓰레드들의 작업이 중단되면 애플리케이션이 멈추기 때문에, GC의 성능 개선을 위해 튜닝을 한다고 하면 보통 stop-the-world의 시간을 줄이는 작업을 하는 것이다. 또한 JVM에서도 이러한 문제를 해결하기 위해 다양한 실행 옵션을 제공하고 있다.

    GC가 수행되는 동안, GC를 수행하는 스레드 이외의 스레드가 중단되어 애플리케이션 중단이 발생하는데, 이를 Stop the World라고 한다.

  • Mark and Sweep

    • Mark: 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업
    • Sweep: Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업
      Stop The World를 통해 모든 작업을 중단시키면, GC는 스택의 모든 변수 또는 Reachable 객체를 스캔하면서 각각이 어떤 객체를 참고하고 있는지를 탐색하게 된다. 그리고 사용되고 있는 메모리를 식별하는데, 이러한 과정을 Mark라고 한다. 이후에 Mark가 되지 않은 객체들을 메모리에서 제거하는데, 이러한 과정을 Sweep라고 한다.

      GC는, 참조되는 변수를 구별하여(Mark), 참조되지 않는 변수를 해제(Sweep)하는 식으로 이뤄진다.

GC의 수행 영역별 구분

GC가 수행되는 영역에 따라 Minor GC 와 Major GC(Full GC) 로 구분한다.

  • Minor GC는 EdenSurvivor1,2Young Generation 영역의 객체를 메모리에서 삭제한다.
    (Young 영역에서 발생하는 GC)

  • Major GC는 Minor GC과정에서 삭제 되지 않고, Old Generation영역으로 옮겨진 객체 중 미사용 된다고 판단되는 객체를 메모리에서 삭제한다.
    (Old 영역이나 Perm 영역에서 발생하는 GC)

    GC의 성능을 최적화하기 위하여 Heap 메모리 영역이 구분되어 있는데,
    크게 Young & Old Generation으로 나눌 수 있고 각 영역에서 Minor GC와 Major GC가 수행된다.

GC 발생 시나리오

GC는 기본적으로 다음과 같은 시나리오로 동작한다.
객체가 생성되면 Eden 영역에 위치 하게 된다.
Eden영역이 가득차게 되면 Minor GC가 발생하여 참조가 없는 객체는 삭제되고, 참조 중인 객체는 Suvvivor 영역으로 이동한다.
Survivor영역이 가득차게 되면 Minor GC가 발생하고 참조가 없는 객체는 삭제되고, 참조 중인 객체는 다른 Suvvivor 영역으로 이동한다.

Survivor영역에서의 GC과정을 반복 하며, 계속 참조 중인 객체는 OLD 영역으로 이동한다.

Eden 영역에서 Survivor 영역으로 이동 할 때 객체가 남아있는 영역보다 큰 경우 OLD 영역으로 이동한다.

GC 수행 방식에 따른 종류와 변화

JVM 버전의 변화에 따라 여러가지 GC방식이 추가 되고 발전되어 왔다.

버전별로 지원하는 GC는 차이가 존재하며, 서비스 상황에 따라 필요한 GC방삭을 JVM옵션을 통한 설정으로 사용이 가능하다.

GC의 종류는 다음과 같다.

Serial Garbage Collector

  • 주로 32비트 JVM에서 돌아가는 싱글쓰레드 어플리케이션에서 사용 (별도로 지정하지 않는 경우 기본 GC)
  • Minor GC 뿐 아니라 Major GC인 경우도 올스탑(stop-the-world)하는 싱글쓰레드 방식.
  • Mark-sweep-compact 알고리즘
    • Old 영역으로 이동된 객체들 중 살아 있는 개체를 식별합니다. (Mark)
    • Old 영역의 객체들을 훑는 작업을 수행하여 쓰레기 객체를 식별합니다. (Sweep)
    • 필요 없는 객체들을 지우고 살아 있는 객체들을 한 곳으로 모은다 (Compact)

Parallel Collector(=Throughput Collector)

  • 64비트 JVM이나 멀티 CPU 유닉스 머신에서 기본 GC로 설정되어 있음.
  • MinorGC와 MajorGC 모두 멀티쓰레드를 사용.
  • MinorGC 뿐 아니라 Major GC인 경우도 올스탑(stop-the-world)
  • Java 1.8의 기본 GC

CMS Collector (Concurrent Mark-Sweep)

  • Initial Mark 단계에서참조 상태인 객체를 짧은 시간에 Marking 후,올스탑 없이 Concurrent Mark단계에서 참조상태 객체를 확인. Remark단계에서 변경되거나 추가된 객체를 확인. Concurrent Sweep 단계애서참조 되지 않는 객체를 정리.
  • CPU리소스를 많이 사용, 메모리 파편화가 단점.
    • Mark 단계 : 매우 짧은 대기 시간으로 살아 있는 객체를 찾는 단계
    • Sweep 단계 : 서버 수행과 동시에 살아 있는 객체에 표시를 해 놓는 단계
    • Remark 단계 : Concurrent 표시 단계에서 표시하는 동안 변경된 객체에 대해서 다시 표시하는 단계
    • Concurrent Sweep 단계 : 표시되어 있는 쓰레기를 정리하는 단계

G1 Collector (Garbage First)

  • 기존 Young, Old 영역의 개념과 다른 Heap에 Resion 개념을 도입함.
  • 하나 이상의 Resion 에서 객체를 복사해 다른 Resion으로 이동 시키는 방식.
  • CMS와 비슷한 방식으로 동작 시작. Heap에 전역적으로 Marking 하고, 가장 많은 공간이 있는 곳 부터 메모리 회수 진행. 이 부분 때문에 Garbage First 라는 이름이 붙었다.
  • CMS Collector의 CPU리소스 및 메모리 파편화의 단점 해결
  • Java 9 부터 기본 GC

Reference

https://mangkyu.tistory.com/118
https://d2.naver.com/helloworld/1329
https://blog.ycpark.net/entry/JAVA%EC%9D%98-GC%EC%9D%98-%EC%A2%85%EB%A5%98-%EB%B0%8F-%ED%8A%B9%EC%A7%95
https://12bme.tistory.com/57
https://www.freecodecamp.org/news/garbage-collection-in-java-what-is-gc-and-how-it-works-in-the-jvm/
https://www.oracle.com/java/technologies/javase/gc-tuning-6.html#introduction

profile
Let's grow together😊
post-custom-banner

0개의 댓글