Garbage Collection(GC) - (1)

겔로그·2022년 9월 4일
3

Java

목록 보기
2/10
post-thumbnail

개요

현재는 기술의 발전으로 인해 하드웨어적인 성능이 좋아져 메모리와 관련된 고민을 크게하지 않지만 사실 개발은 제한된 메모리 영역 내에서 문제 해결을 해야 됩니다. 제한된 공간에서 문제를 해결하기 위해선 메모리 관리가 필수적이며 개발 언어별로 메모리를 관리하는 법은 다양합니다.

한 예시로 C언어를 통해 개발을 하다보면 동적으로 할당한 메모리들은 free()라는 함수로 해제하는 과정을 거치지만, Java로 개발할 경우 할당된 메모리들에 대해 Garbage Collector(GC) 가 자동으로 불필요한 메모리를 수집해 해제해주는 역할을 담당하고 있습니다.

Java 개발자로서 Garbage Collection에 대해 이해해야 될 의무가 있을 것 같아 Garbage Collector는 어떤 방식으로 동작하고, JDK 버전별로 사용하는 GC가 다른데 각각 어떤 방식으로 동작하는지 알아보는 시간을 가지겠습니다.

Garbage Collection이란?

Garbage Collection은 동적으로 할당된 메모리 영역 가운데 더이상 사용하지 않거나 불필요하게 할당된 영역을 탐지하여 자동으로 해제하는 기법입니다. 개발자는 Garbage Collection을 이용할 경우 다음과 같은 장단점을 가지고 있습니다.

Garbage Collection 장점

  • 유효하지 않은 포인터 접근: 이미 해제된 메모리에 접근하는 버그를 방지한다.
  • 이중 해제: 이미 해제된 메모리를 또 다시 해제하는 버그를 방지
  • Memory leak 방지: 더 이상 필요하지 않은 메모리가 해제되지 않고 남아있어 최종적으로 메모리가 부족해 프로그램이 중단되는 버그 방지.

Garbage Collection 단점

  • Garbage Collection이 발생하는 시점을 예측하기가 어렵다.
  • 할당된 메모리가 해제된 시점을 알 수 없다.
  • 메모리 해제를 위해 어떠한 메모리를 수집하고 해제할 것인지 결정하는데 비용이 든다.
  • GC가 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생할 수 있다.
    => (stop-the-world(stw))

GC의 Garbage 수집 방식

GC는 동적으로 할당한 메모리에 대해 메모리를 효율적으로 관리해주는 수단이라는 것을 앞의 정의를 통해 확인했습니다. 그렇다면 GC는 어떻게 어떤 메모리를 해제할지 아는 것일까요?

프로세스 메모리 영역

기본적으로 프로세스의 메모리 영역은 크게 `CODE,DATA,HEAP,STACK` section으로 이루어졌습니다. 동적으로 할당한 메모리는 **Heap 영역에서 관리**하는데 대표적으로 **new** 명령어로 생성되는 모든 객체는 이 Heap 영역에 할당된다고 보면 됩니다.

Heap

JVM의 Heap 영역은 처음 설계될 당시 두 가지 전제를 바탕으로 설계되었습니다.

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

객체는 대부분 호출할 당시에만 사용하는 일회성이며 호출한 이후 지속적으로 사용되어 메모리에 오랫동안 남아있는 경우는 드뭅니다. 이러한 점을 고려해 참조 시간에 따라 Young과 Old로 구분하여 설계하였습니다.

Heap 영역

  • Young
  • Old

Reference counting

다시 본론으로 돌아가면 객체를 Heap영역에 메모리를 할당하여 생성을 하고, 사용을 위해 해당 메모리를 참조하는 참조 변수 를 이용해 객체가 생성된 메모리에 접근한다고 이해하시면 좋을 것 같습니다.

만약 해당 객체를 참조하는 서비스가 종료되거나, 호출한 함수가 종료될 경우 해당 참조는 종료됩니다.

GC는 이러한 점을 참고하여 Heap 영역에 존재하지만 더 이상 참조하는 곳이 없을 경우 메모리 할당을 해제하기 위해 해당 메모리 영역을 수집해갑니다.

이제 GC가 참조 변수의 참조 여부에 따라 heap 영역에 할당된 객체들을 해제할지 확인하는 것을 알았습니다. 그럼 이제 수집한 내용을 어떤 방식으로 해제하는지 알아보겠습니다.

Minor GC

GC는 영역별로 크게 두 가지로 나뉘어 진행됩니다.

  • Minor GC => Young 영역
  • Major GC => Old 영역

Young 영역은 크게 3가지 영역으로 나누어집니다.

Young 영역

  • Eden 영역
  • Survivor 영역(2개, Survivor1, Survivor2)

각 영역이 어떤 방식으로 동작하는지 처리 절차를 순서에 따라 기술하면 다음과 같습니다.

처리 순서

  1. 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
  2. Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동한다.
  3. 1~2 동작을 계속해서 반복할 경우 Survivor 영역으로 객체가 계속 쌓이고 최종적으로 가득 차게 된다.
  4. 하나의 Survivor 영역이 가득차게 되면 GC를 진행하고 이 중 살아남은 객체는 다른 Survivor 영역으로 이동한다.(Survivor에서 GC가 발생할 경우, 해당 영역은 아무것도 없는 빈 상태가 된다)
  5. 1-4 과정을 반복하다가 끝까지 살아남은 객체들이 최종적으로 Old 영역으로 이동한다.

## Minor GC 과정을 거쳐 Old 영역으로 이동하는 과정

추가로 알면 좋을 내용은 bump-the-pointer와 TLABs가 있는데 해당 내용을 알고 싶을 경우 [Naver D2 - Java Garbage Collection](https://d2.naver.com/helloworld/1329를 참고해보시면 좋을 것 같습니다. 이 곳에서는 간단하게 용어 정의만 하고 넘어가겠습니다.

bump-the-pointer: 객체가 생성될 경우 해당 객체가 Eden 영역에 넣기 적당한지 판단하는 기술이다.

TLABs: 멀티 스레드 환경에서는 Eden 영역을 사용할 경우 필연적으로 lock이 발생할 수 밖에 없기 때문에 이런 부분을 방지해주는 기술이다. Eden영역을 스레드에게 각각 영역을 나누어 할당해주고 해당 영역을 사용할 수 있도록 도와준다. 이를 통해 lock이 발생하지 않고 GC 이용이 가능해진다.

Major GC

Major GC는 Minor GC가 반복해 최종적으로 Old 영역의 모든 메모리가 할당되었을 경우 발생한다. Major GC에는 여러 종류의 GC가 존재합니다.

  • Serial GC
  • Parallel GC
  • Parallel Old GC
  • CMS GC
  • Shenandoah GC
  • Epsilon GC
  • G1 GC
  • ZGC

Major GC 종류로 다양한 GC들이 존재합니다. 빨간색으로 색칠한 GC들은 JDK 9 버전 이후에 출시된 GC들이며, 이들은 Minor GC, Major GC이면서 Full GC의 역할도 수행합니다.

Full GC란?

Full GC란 Minor GC와 Major GC의 역할을 통합해서 진행하는 GC를 의미합니다. 이는 JVM과 GC 문서에서 표준으로 부르는 용어는 아니지만, 새로운 개념에 대한 이해를 돕고 있어 많은 사람들이 사용하는 용어입니다.

JDK 9버전 이후에 고안된 GC들은 Minor, Major, Full의 개념을 복합적으로 이용해 이 GC가 어떤 GC이다라고 확실하게 대답하기엔 매우 어려워졌습니다. 현대의 GC에서는 쓰레기를 수집한다는 개념만 똑같다는 것을 참고해주시길 바랍니다.

참고: minor-gc-major-gc-full-gc

JDK 버전별 기본 GC

  • JDK 7: Parallel GC
  • JDK 8: Parallel GC
  • JDK 9: G1 GC
  • JDK 10: G1 GC
  • JDK 11: G1 GC

그렇다면 각각의 GC에 대해 이해하고 어떤 방식으로 동작하는지 확인하는 시간을 가져보겠습니다.

하지만 그 전에 Major GC의 대표적인 동작 원리에 대해 먼저 알아보고 가겠습니다.

Mark-Sweep-Compact Algorithm

Mark-Sweep-Compact 알고리즘은 Garbage Collection 이 동작하는 원리로 루트에서부터 해당 객체에 접근 가능한지에 대한 여부를 메모리 해제의 기준으로 삼습니다.

Mark-Sweep-Compact 정의

Mark : Root로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
Sweep : 참조하고 있지 않은 객체 즉 Unreachable 객체들을 Heap에서 제거한다.
Compact : Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다. (Major GC 종류에 따라 하지 않는 경우도 있다.)

이제 진짜로 Major GC를 종류별로 알아가보는 시간을 가지겠습니다.

Serial GC

mark-sweep-compact 알고리즘을 사용하며 적은 메모리와 CPU 코어 개수가 적을 때 사용하는 방식(단일 스레드 사용)입니다. 모든 JDK 버전에서 단일 코어일 경우 해당 GC가 Default로 적용됩니다.

Parallel GC

Serial GC와 기본적은 알고리즘은 같지만 GC를 수행하는 쓰레드 수가 여러 개입니다. 메모리가 충분하고 CPU 코어 갯수가 많을 때 유리하며 JDK 8 버전까지 default GC로 사용되었습니다.

Parallel Old GC

Parallel GC와 비교해 Old 영역의 GC 알고리즘만 다른 GC입니다. Mark-Summary-Compaction 단계를 거치는데, 위에서 소개한 Mark-Sweep-Compaction 알고리즘의 Sweep 부분이 달라졌으며 약간 더 복잡해집니다.

CMS GC

모든 애플리케이션의 응답 속도가 매우 중요할 때 사용하는 GC로 Low Latency GC라고도 부릅니다.

GC 과정 중 진행 중이던 어플리케이션 thread는 mark 단계 및 remark 단계에서 짧게 STW가 발생하지만, GC 수행 중에도 실행된다는 장점이 존재합니다. 해당 GC는 JDK 14 버전부터 타 GC의 발전으로 GC 목록에서 제외되었습니다.

CMS GC 처리과정

  1. Initial-mask 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아있는 객체만 찾는다. (STW 발생)
  2. Concurrent Mark 단계에서 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가며 확인한다. 이 때 다른 스레드가 실행 중인 상태에서 동시에 진행된다.
  3. Remark 단계에서 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다. (STW 발생)
  4. Concurrent Sweep 단계에서 쓰레기를 정리하는 작업을 실행한다.

CMS GC는 어플리케이션 쓰레드가 STW가 최소화되는 이점이 존재하지만 다른 GC 방식보다 메모리와 CPU를 더 많이 사용하며 Compaction 단계를 제공하지 않아 조각난 메모리가 많이 존재하게 되는 단점이 있습니다.

따라서 이용시에 얼마나 자주 수행되며 시간이 얼마나 걸리는지 측정해보고 사용해보는 것을 권장하고 있습니다.

결론

지금까지는 JDK 8까지 이용해왔던 GC에 대해 알아보는 시간을 가졌습니다. 전통적인 구조였던 Young과 Old 구간을 메모리상에서 물리적으로 분리해 메모리 수집을 진행하였습니다.

다음 장에서 볼 내용은 JDK 9 버전 이후의 GC이며 어떻게 변경이 되었는지 알아보는 시간을 가져보겠습니다. 감사합니다.

추가 자료 - GC 용어

Stop-the-world(STW)

GC를 수행하는 동안 어플리케이션의 모든 thread 가 중단된다. GC는 일반적으로 Old 영역이 가득찰 경우 발생하기 때문에 STW는 예측이 불가능한 시점에 발생한다.

Concurrent

GC의 thread와 어플리케이션 thread는 동시에 실행될 수 없다.

Parallel

Garbage Collection은 STW를 발생시키기 때문에 최대한 빨리 처리해야 하므로 여러 thread 를 동원해서 진행한다.

Exact

정확한 GC scheme(계획, 전략)은 전체 garbage 를 한 cycle에 수집할 수 있게 heap 상태에 관한 충분한 type 정보를 지니고 있다.

Conservative

conservative scheme 은 정확한 정보가 없다.
그래서 resource 낭비가 잦고 type 체계를 무시하기 때문에 비효율적이다.

Moving

이동 수집기(moving collector)에서 객체는 메모리를 이동시킬 수 있다..
객체 주소는 고정된 것이 아니기 때문에 raw pointer로 직접 access 하는 환경은 이동 수집기와 잘 맞지 않다.

Compacting

GC cycle 마지막에 allocated memory들은 동일한 영역으로 (대개 이 영역 첫 부분) 배열된다.

Evacuating

GC cycle 에서 마지막에 할당된 영역을 비우고 살아남은 객체를 다른 memory 영역으로 방출한다.

Reference

profile
Gelog 나쁜 것만 드려요~

0개의 댓글